From 762f32dfeb5c9555ee6b58f3abc0715c5d13392b Mon Sep 17 00:00:00 2001 From: Johannes Rudolph Date: Wed, 11 Jun 2014 09:26:07 +0200 Subject: [PATCH 1/8] =hco,str small cleanups --- .../src/main/scala/akka/http/model/parser/AcceptHeader.scala | 3 ++- akka-http-core/src/main/scala/akka/http/util/DateTime.scala | 2 +- akka-stream/src/test/scala/akka/stream/FlowConcatSpec.scala | 2 -- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/akka-http-core/src/main/scala/akka/http/model/parser/AcceptHeader.scala b/akka-http-core/src/main/scala/akka/http/model/parser/AcceptHeader.scala index fbab643449..611f444ee0 100644 --- a/akka-http-core/src/main/scala/akka/http/model/parser/AcceptHeader.scala +++ b/akka-http-core/src/main/scala/akka/http/model/parser/AcceptHeader.scala @@ -13,7 +13,8 @@ private[parser] trait AcceptHeader { this: Parser with CommonRules with CommonAc // http://tools.ietf.org/html/rfc7231#section-5.3.2 def accept = rule { - zeroOrMore(`media-range-decl`).separatedBy(listSep) ~ EOI ~> (Accept(_: _*)) + + zeroOrMore(`media-range-decl`).separatedBy(listSep) ~ EOI ~> (Accept(_)) } def `media-range-decl` = rule { diff --git a/akka-http-core/src/main/scala/akka/http/util/DateTime.scala b/akka-http-core/src/main/scala/akka/http/util/DateTime.scala index d16a50576c..dcaa701f67 100644 --- a/akka-http-core/src/main/scala/akka/http/util/DateTime.scala +++ b/akka-http-core/src/main/scala/akka/http/util/DateTime.scala @@ -25,7 +25,7 @@ final case class DateTime private (year: Int, // the year def weekdayStr: String = DateTime.weekday(weekday) /** - * The day of the month as a 3 letter abbreviation: + * The month as a 3 letter abbreviation: * `Jan`, `Feb`, `Mar`, `Apr`, `May`, `Jun`, `Jul`, `Aug`, `Sep`, `Oct`, `Nov` or `Dec` */ def monthStr: String = DateTime.month(month - 1) diff --git a/akka-stream/src/test/scala/akka/stream/FlowConcatSpec.scala b/akka-stream/src/test/scala/akka/stream/FlowConcatSpec.scala index 21e6ec88fd..c377a14d46 100644 --- a/akka-stream/src/test/scala/akka/stream/FlowConcatSpec.scala +++ b/akka-stream/src/test/scala/akka/stream/FlowConcatSpec.scala @@ -6,8 +6,6 @@ package akka.stream import akka.stream.testkit.StreamTestKit import akka.stream.scaladsl.Flow import org.reactivestreams.api.Producer -import akka.stream.testkit.OnSubscribe -import akka.stream.testkit.OnError import scala.concurrent.Promise class FlowConcatSpec extends TwoStreamsSetup { From f3c4e01f2ac125d843ab0a2d084a4368fa79448d Mon Sep 17 00:00:00 2001 From: Johannes Rudolph Date: Wed, 11 Jun 2014 09:52:42 +0200 Subject: [PATCH 2/8] +hco introduce Uri.Host.inetAddresses for easy access to the actual addresses --- .../src/main/scala/akka/http/model/Uri.scala | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/akka-http-core/src/main/scala/akka/http/model/Uri.scala b/akka-http-core/src/main/scala/akka/http/model/Uri.scala index 04736ecd4d..d38190b25b 100644 --- a/akka-http-core/src/main/scala/akka/http/model/Uri.scala +++ b/akka-http-core/src/main/scala/akka/http/model/Uri.scala @@ -15,6 +15,7 @@ import akka.http.model.parser.UriParser import akka.http.model.parser.CharacterClasses._ import akka.http.util._ import Uri._ +import java.net.InetAddress /** * An immutable model of an internet URI as defined by http://tools.ietf.org/html/rfc3986. @@ -308,6 +309,7 @@ object Uri { def address: String def isEmpty: Boolean def toOption: Option[NonEmptyHost] + def inetAddresses: immutable.Seq[InetAddress] def equalsIgnoreCase(other: Host): Boolean override def toString() = UriRendering.HostRenderer.render(new StringRendering, this).get } @@ -316,6 +318,8 @@ object Uri { def address: String = "" def isEmpty = true def toOption = None + def inetAddresses: immutable.Seq[InetAddress] = Nil + def equalsIgnoreCase(other: Host): Boolean = other eq this } def apply(string: String, charset: Charset = UTF8, mode: Uri.ParsingMode = Uri.ParsingMode.Relaxed): Host = @@ -332,6 +336,8 @@ object Uri { case IPv4Host(`bytes`, _) ⇒ true case _ ⇒ false } + + def inetAddresses = immutable.Seq(InetAddress.getByAddress(bytes.toArray)) } object IPv4Host { def apply(address: String): IPv4Host = apply(address.split('.').map(_.toInt.toByte)) @@ -346,6 +352,8 @@ object Uri { case IPv6Host(`bytes`, _) ⇒ true case _ ⇒ false } + + def inetAddresses = immutable.Seq(InetAddress.getByAddress(bytes.toArray)) } object IPv6Host { def apply(bytes: String, address: String): IPv6Host = { @@ -360,6 +368,8 @@ object Uri { case NamedHost(otherAddress) ⇒ address equalsIgnoreCase otherAddress case _ ⇒ false } + + def inetAddresses = InetAddress.getAllByName(address).toList } sealed abstract class Path { From 4b2e79d853fd8ef62794835b5a5f93b2d2f77460 Mon Sep 17 00:00:00 2001 From: Johannes Rudolph Date: Wed, 11 Jun 2014 09:55:32 +0200 Subject: [PATCH 3/8] =hco more accurate result types for HttpEntity.apply overloads --- .../main/scala/akka/http/model/HttpEntity.scala | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/akka-http-core/src/main/scala/akka/http/model/HttpEntity.scala b/akka-http-core/src/main/scala/akka/http/model/HttpEntity.scala index c8979192a2..3f97fe120e 100644 --- a/akka-http-core/src/main/scala/akka/http/model/HttpEntity.scala +++ b/akka-http-core/src/main/scala/akka/http/model/HttpEntity.scala @@ -60,14 +60,14 @@ sealed trait HttpEntity { } object HttpEntity { - implicit def apply(string: String): Regular = apply(ContentTypes.`text/plain(UTF-8)`, string) - implicit def apply(bytes: Array[Byte]): Regular = apply(ContentTypes.`application/octet-stream`, bytes) - implicit def apply(data: ByteString): Regular = apply(ContentTypes.`application/octet-stream`, data) - def apply(contentType: ContentType, string: String): Regular = + implicit def apply(string: String): Strict = apply(ContentTypes.`text/plain(UTF-8)`, string) + implicit def apply(bytes: Array[Byte]): Strict = apply(ContentTypes.`application/octet-stream`, bytes) + implicit def apply(data: ByteString): Strict = apply(ContentTypes.`application/octet-stream`, data) + def apply(contentType: ContentType, string: String): Strict = if (string.isEmpty) empty(contentType) else apply(contentType, ByteString(string.getBytes(contentType.charset.nioCharset))) - def apply(contentType: ContentType, bytes: Array[Byte]): Regular = + def apply(contentType: ContentType, bytes: Array[Byte]): Strict = if (bytes.length == 0) empty(contentType) else apply(contentType, ByteString(bytes)) - def apply(contentType: ContentType, data: ByteString): Regular = + def apply(contentType: ContentType, data: ByteString): Strict = if (data.isEmpty) empty(contentType) else Strict(contentType, data) def apply(contentType: ContentType, contentLength: Long, data: Producer[ByteString]): Regular = if (contentLength == 0) empty(contentType) else Default(contentType, contentLength, data) @@ -78,7 +78,7 @@ object HttpEntity { else empty(contentType) } - val Empty = Strict(ContentTypes.NoContentType, data = ByteString.empty) + val Empty: Strict = Strict(ContentTypes.NoContentType, data = ByteString.empty) def empty(contentType: ContentType): Strict = if (contentType == Empty.contentType) Empty From 6d86360b4fba72b5ebd4496629a54c2e89be1dff Mon Sep 17 00:00:00 2001 From: Johannes Rudolph Date: Wed, 11 Jun 2014 09:56:25 +0200 Subject: [PATCH 4/8] +hco introduce generic abstract accessor for HttpCredentials parameters --- .../http/model/headers/HttpCredentials.scala | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/akka-http-core/src/main/scala/akka/http/model/headers/HttpCredentials.scala b/akka-http-core/src/main/scala/akka/http/model/headers/HttpCredentials.scala index d2dec483f2..b8a7975a1a 100644 --- a/akka-http-core/src/main/scala/akka/http/model/headers/HttpCredentials.scala +++ b/akka-http-core/src/main/scala/akka/http/model/headers/HttpCredentials.scala @@ -9,15 +9,23 @@ import akka.parboiled2.util.Base64 import akka.http.model.HttpCharsets._ import akka.http.util.{ Rendering, ValueRenderable } -sealed trait HttpCredentials extends ValueRenderable +sealed trait HttpCredentials extends ValueRenderable { + def scheme: String + def token: String + def params: Map[String, String] +} final case class BasicHttpCredentials(username: String, password: String) extends HttpCredentials { - def render[R <: Rendering](r: R): r.type = { + val cookie = { val userPass = username + ':' + password val bytes = userPass.getBytes(`ISO-8859-1`.nioCharset) - val cookie = Base64.rfc2045.encodeToChar(bytes, false) - r ~~ "Basic " ~~ cookie + Base64.rfc2045.encodeToChar(bytes, false) } + def render[R <: Rendering](r: R): r.type = r ~~ "Basic " ~~ cookie + + def scheme: String = "Basic" + def token = cookie.toString + def params = Map.empty } object BasicHttpCredentials { @@ -33,6 +41,9 @@ object BasicHttpCredentials { final case class OAuth2BearerToken(token: String) extends HttpCredentials { def render[R <: Rendering](r: R): r.type = r ~~ "Bearer " ~~ token + + def scheme: String = "Bearer" + def params: Map[String, String] = Map.empty } final case class GenericHttpCredentials(scheme: String, token: String, From f3d5af533a618ab85822dbafb1dd8717c1425686 Mon Sep 17 00:00:00 2001 From: Johannes Rudolph Date: Wed, 11 Jun 2014 09:57:59 +0200 Subject: [PATCH 5/8] !hco more stringent following of plurally named container object - ContentDispositionType => ContentDispositionTypes - TransferEncoding => TransferEncodings - RangeUnit => RangeUnits --- .../main/scala/akka/http/model/HttpForm.scala | 2 +- .../akka/http/model/MultipartContent.scala | 4 +-- .../headers/ContentDispositionType.scala | 12 ++++--- .../akka/http/model/headers/HttpOrigin.scala | 4 +-- .../akka/http/model/headers/LinkValue.scala | 4 +-- .../akka/http/model/headers/RangeUnit.scala | 2 +- .../http/model/headers/TransferEncoding.scala | 20 +++++++---- .../akka/http/model/headers/headers.scala | 7 ++-- .../akka/http/model/parser/CommonRules.scala | 14 ++++---- .../parser/ContentDispositionHeader.scala | 8 ++--- .../akka/http/model/parser/LinkHeader.scala | 30 ++++++++-------- .../http/model/parser/HttpHeaderSpec.scala | 34 +++++++++---------- .../akka/http/parsing/RequestParserSpec.scala | 4 +-- 13 files changed, 78 insertions(+), 67 deletions(-) diff --git a/akka-http-core/src/main/scala/akka/http/model/HttpForm.scala b/akka-http-core/src/main/scala/akka/http/model/HttpForm.scala index 1d0903f07f..c495cddae0 100644 --- a/akka-http-core/src/main/scala/akka/http/model/HttpForm.scala +++ b/akka-http-core/src/main/scala/akka/http/model/HttpForm.scala @@ -41,7 +41,7 @@ object MultipartFormData { def apply(fields: Map[String, BodyPart]): MultipartFormData = apply { fields.map { - case (key, value) ⇒ value.copy(headers = `Content-Disposition`(ContentDispositionType.`form-data`, Map("name" -> key)) +: value.headers) + case (key, value) ⇒ value.copy(headers = `Content-Disposition`(ContentDispositionTypes.`form-data`, Map("name" -> key)) +: value.headers) }(collection.breakOut): _* } } diff --git a/akka-http-core/src/main/scala/akka/http/model/MultipartContent.scala b/akka-http-core/src/main/scala/akka/http/model/MultipartContent.scala index 4555754896..61c076000a 100644 --- a/akka-http-core/src/main/scala/akka/http/model/MultipartContent.scala +++ b/akka-http-core/src/main/scala/akka/http/model/MultipartContent.scala @@ -55,7 +55,7 @@ final case class BodyPart(entity: HttpEntity, headers: immutable.Seq[HttpHeader] def dispositionParameterValue(parameter: String): Option[String] = headers.collectFirst { - case `Content-Disposition`(ContentDispositionType.`form-data`, parameters) if parameters.contains(parameter) ⇒ + case `Content-Disposition`(ContentDispositionTypes.`form-data`, parameters) if parameters.contains(parameter) ⇒ parameters(parameter) } @@ -78,5 +78,5 @@ object BodyPart { def apply(entity: HttpEntity, fieldName: String): BodyPart = apply(entity, fieldName, Map.empty[String, String]) def apply(entity: HttpEntity, fieldName: String, parameters: Map[String, String]): BodyPart = - BodyPart(entity, immutable.Seq(`Content-Disposition`(ContentDispositionType.`form-data`, parameters.updated("name", fieldName)))) + BodyPart(entity, immutable.Seq(`Content-Disposition`(ContentDispositionTypes.`form-data`, parameters.updated("name", fieldName)))) } \ No newline at end of file diff --git a/akka-http-core/src/main/scala/akka/http/model/headers/ContentDispositionType.scala b/akka-http-core/src/main/scala/akka/http/model/headers/ContentDispositionType.scala index 6385191bff..a185f3d287 100644 --- a/akka-http-core/src/main/scala/akka/http/model/headers/ContentDispositionType.scala +++ b/akka-http-core/src/main/scala/akka/http/model/headers/ContentDispositionType.scala @@ -8,10 +8,14 @@ import akka.http.util.{ Rendering, SingletonValueRenderable, Renderable } sealed trait ContentDispositionType extends Renderable -object ContentDispositionType { - case object inline extends ContentDispositionType with SingletonValueRenderable - case object attachment extends ContentDispositionType with SingletonValueRenderable - case object `form-data` extends ContentDispositionType with SingletonValueRenderable +object ContentDispositionTypes { + protected abstract class Predefined extends ContentDispositionType with SingletonValueRenderable { + def name: String = value + } + + case object inline extends Predefined + case object attachment extends Predefined + case object `form-data` extends Predefined final case class Ext(name: String) extends ContentDispositionType { def render[R <: Rendering](r: R): r.type = r ~~ name } diff --git a/akka-http-core/src/main/scala/akka/http/model/headers/HttpOrigin.scala b/akka-http-core/src/main/scala/akka/http/model/headers/HttpOrigin.scala index 40bc785f2f..0b92fc2b6f 100644 --- a/akka-http-core/src/main/scala/akka/http/model/headers/HttpOrigin.scala +++ b/akka-http-core/src/main/scala/akka/http/model/headers/HttpOrigin.scala @@ -15,8 +15,6 @@ abstract class HttpOriginRange extends ValueRenderable { def matches(origin: HttpOrigin): Boolean } object HttpOriginRange { - implicit val originsRenderer: Renderer[immutable.Seq[HttpOrigin]] = Renderer.seqRenderer(" ", "null") - case object `*` extends HttpOriginRange { def matches(origin: HttpOrigin) = true def render[R <: Rendering](r: R): r.type = r ~~ '*' @@ -34,6 +32,8 @@ final case class HttpOrigin(scheme: String, host: Host) extends ValueRenderable def render[R <: Rendering](r: R): r.type = host.renderValue(r ~~ scheme ~~ "://") } object HttpOrigin { + implicit val originsRenderer: Renderer[immutable.Seq[HttpOrigin]] = Renderer.seqRenderer(" ", "null") + implicit def apply(str: String): HttpOrigin = { val parser = new UriParser(str, UTF8, Uri.ParsingMode.Relaxed) parser.parseOrigin() diff --git a/akka-http-core/src/main/scala/akka/http/model/headers/LinkValue.scala b/akka-http-core/src/main/scala/akka/http/model/headers/LinkValue.scala index 96773583d6..007e99a641 100644 --- a/akka-http-core/src/main/scala/akka/http/model/headers/LinkValue.scala +++ b/akka-http-core/src/main/scala/akka/http/model/headers/LinkValue.scala @@ -11,7 +11,6 @@ import akka.http.util._ import UriRendering.UriRenderer final case class LinkValue(uri: Uri, params: immutable.Seq[LinkParam]) extends ValueRenderable { - import LinkParam.paramsRenderer def render[R <: Rendering](r: R): r.type = { r ~~ '<' ~~ uri ~~ '>' if (params.nonEmpty) r ~~ "; " ~~ params @@ -24,10 +23,11 @@ object LinkValue { } sealed abstract class LinkParam extends ToStringRenderable - object LinkParam { implicit val paramsRenderer: Renderer[immutable.Seq[LinkParam]] = Renderer.seqRenderer(separator = "; ") +} +object LinkParams { private val reserved = CharPredicate(" ,;") // A few convenience rels diff --git a/akka-http-core/src/main/scala/akka/http/model/headers/RangeUnit.scala b/akka-http-core/src/main/scala/akka/http/model/headers/RangeUnit.scala index aed2502899..c122e9f25b 100644 --- a/akka-http-core/src/main/scala/akka/http/model/headers/RangeUnit.scala +++ b/akka-http-core/src/main/scala/akka/http/model/headers/RangeUnit.scala @@ -8,7 +8,7 @@ import akka.http.util.{ Rendering, ValueRenderable } sealed trait RangeUnit extends ValueRenderable -object RangeUnit { +object RangeUnits { object Bytes extends RangeUnit { def render[R <: Rendering](r: R): r.type = r ~~ "bytes" } diff --git a/akka-http-core/src/main/scala/akka/http/model/headers/TransferEncoding.scala b/akka-http-core/src/main/scala/akka/http/model/headers/TransferEncoding.scala index cab5e3fa0e..d8e23ff8bc 100644 --- a/akka-http-core/src/main/scala/akka/http/model/headers/TransferEncoding.scala +++ b/akka-http-core/src/main/scala/akka/http/model/headers/TransferEncoding.scala @@ -6,13 +6,21 @@ package akka.http.model.headers import akka.http.util.{ Rendering, SingletonValueRenderable, Renderable } -sealed trait TransferEncoding extends Renderable +sealed trait TransferEncoding extends Renderable { + def name: String + def params: Map[String, String] +} -object TransferEncoding { - case object chunked extends TransferEncoding with SingletonValueRenderable - case object compress extends TransferEncoding with SingletonValueRenderable - case object deflate extends TransferEncoding with SingletonValueRenderable - case object gzip extends TransferEncoding with SingletonValueRenderable +object TransferEncodings { + protected abstract class Predefined extends TransferEncoding with SingletonValueRenderable { + def name: String = value + def params: Map[String, String] = Map.empty + } + + case object chunked extends Predefined + case object compress extends Predefined + case object deflate extends Predefined + case object gzip extends Predefined final case class Extension(name: String, params: Map[String, String] = Map.empty) extends TransferEncoding { def render[R <: Rendering](r: R): r.type = { r ~~ name 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 5236da12d8..72b7454675 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 @@ -245,7 +245,7 @@ final case class `Content-Length`(length: Long)(implicit ev: AllowProtectedHeade // http://tools.ietf.org/html/rfc7233#section-4.2 object `Content-Range` extends ModeledCompanion { - def apply(byteContentRange: ByteContentRange): `Content-Range` = apply(RangeUnit.Bytes, byteContentRange) + def apply(byteContentRange: ByteContentRange): `Content-Range` = apply(RangeUnits.Bytes, byteContentRange) } final case class `Content-Range`(rangeUnit: RangeUnit, contentRange: ContentRange) extends ModeledHeader { protected def renderValue[R <: Rendering](r: R): r.type = r ~~ rangeUnit ~~ ' ' ~~ contentRange @@ -398,7 +398,6 @@ object Origin extends ModeledCompanion { def apply(first: HttpOrigin, more: HttpOrigin*): Origin = apply(immutable.Seq(first +: more: _*)) } final case class Origin(origins: immutable.Seq[HttpOrigin]) extends ModeledHeader { - import HttpOriginRange.originsRenderer protected def renderValue[R <: Rendering](r: R): r.type = r ~~ origins protected def companion = Origin } @@ -406,7 +405,7 @@ final case class Origin(origins: immutable.Seq[HttpOrigin]) extends ModeledHeade // http://tools.ietf.org/html/rfc7233#section-3.1 object Range extends ModeledCompanion { def apply(first: ByteRange, more: ByteRange*): Range = apply(immutable.Seq(first +: more: _*)) - def apply(ranges: immutable.Seq[ByteRange]): Range = Range(RangeUnit.Bytes, ranges) + def apply(ranges: immutable.Seq[ByteRange]): Range = Range(RangeUnits.Bytes, ranges) private[http] implicit val rangesRenderer = Renderer.defaultSeqRenderer[ByteRange] // cache } final case class Range(rangeUnit: RangeUnit, ranges: immutable.Seq[ByteRange]) extends ModeledHeader { @@ -477,7 +476,7 @@ object `Transfer-Encoding` extends ModeledCompanion { final case class `Transfer-Encoding`(encodings: immutable.Seq[TransferEncoding])(implicit ev: AllowProtectedHeaderCreation.Enabled) extends ModeledHeader { import `Transfer-Encoding`.encodingsRenderer require(encodings.nonEmpty, "encodings must not be empty") - def hasChunked: Boolean = encodings contains TransferEncoding.chunked + def hasChunked: Boolean = encodings contains TransferEncodings.chunked protected def renderValue[R <: Rendering](r: R): r.type = r ~~ encodings protected def companion = `Transfer-Encoding` } diff --git a/akka-http-core/src/main/scala/akka/http/model/parser/CommonRules.scala b/akka-http-core/src/main/scala/akka/http/model/parser/CommonRules.scala index 5eb07023cb..9fb4e4ebda 100644 --- a/akka-http-core/src/main/scala/akka/http/model/parser/CommonRules.scala +++ b/akka-http-core/src/main/scala/akka/http/model/parser/CommonRules.scala @@ -305,7 +305,7 @@ private[parser] trait CommonRules { this: Parser with StringBuilding ⇒ def `byte-ranges-specifier` = rule { `bytes-unit` ~ ws('=') ~ `byte-range-set` } - def `bytes-unit` = rule { "bytes" ~ OWS ~ push(RangeUnit.Bytes) } + def `bytes-unit` = rule { "bytes" ~ OWS ~ push(RangeUnits.Bytes) } def `complete-length` = rule { longNumberCapped } @@ -319,7 +319,7 @@ private[parser] trait CommonRules { this: Parser with StringBuilding ⇒ def `other-range-set` = rule { oneOrMore(VCHAR) ~ OWS } - def `other-range-unit` = rule { token ~> RangeUnit.Other } + def `other-range-unit` = rule { token ~> RangeUnits.Other } def `other-ranges-specifier` = rule { `other-range-unit` ~ ws('=') ~ `other-range-set` } @@ -353,14 +353,14 @@ private[parser] trait CommonRules { this: Parser with StringBuilding ⇒ // ****************************************************************************************** def `transfer-coding` = rule( - ignoreCase("chunked") ~ OWS ~ push(TransferEncoding.chunked) - | ignoreCase("gzip") ~ OWS ~ push(TransferEncoding.gzip) - | ignoreCase("deflate") ~ OWS ~ push(TransferEncoding.deflate) - | ignoreCase("compress") ~ OWS ~ push(TransferEncoding.compress) + ignoreCase("chunked") ~ OWS ~ push(TransferEncodings.chunked) + | ignoreCase("gzip") ~ OWS ~ push(TransferEncodings.gzip) + | ignoreCase("deflate") ~ OWS ~ push(TransferEncodings.deflate) + | ignoreCase("compress") ~ OWS ~ push(TransferEncodings.compress) | `transfer-extension`) def `transfer-extension` = rule { - token ~ zeroOrMore(ws(';') ~ `transfer-parameter`) ~> (_.toMap) ~> (TransferEncoding.Extension(_, _)) + token ~ zeroOrMore(ws(';') ~ `transfer-parameter`) ~> (_.toMap) ~> (TransferEncodings.Extension(_, _)) } def `transfer-parameter` = rule { token ~ ws('=') ~ word ~> (_ -> _) } diff --git a/akka-http-core/src/main/scala/akka/http/model/parser/ContentDispositionHeader.scala b/akka-http-core/src/main/scala/akka/http/model/parser/ContentDispositionHeader.scala index 51306d3913..18266b6001 100644 --- a/akka-http-core/src/main/scala/akka/http/model/parser/ContentDispositionHeader.scala +++ b/akka-http-core/src/main/scala/akka/http/model/parser/ContentDispositionHeader.scala @@ -17,10 +17,10 @@ private[parser] trait ContentDispositionHeader { this: Parser with CommonRules w } def `disposition-type` = rule( - ignoreCase("inline") ~ OWS ~ push(ContentDispositionType.inline) - | ignoreCase("attachment") ~ OWS ~ push(ContentDispositionType.attachment) - | ignoreCase("form-data") ~ OWS ~ push(ContentDispositionType.`form-data`) - | `disp-ext-type` ~> (ContentDispositionType.Ext(_))) + ignoreCase("inline") ~ OWS ~ push(ContentDispositionTypes.inline) + | ignoreCase("attachment") ~ OWS ~ push(ContentDispositionTypes.attachment) + | ignoreCase("form-data") ~ OWS ~ push(ContentDispositionTypes.`form-data`) + | `disp-ext-type` ~> (ContentDispositionTypes.Ext(_))) def `disp-ext-type` = rule { token } diff --git a/akka-http-core/src/main/scala/akka/http/model/parser/LinkHeader.scala b/akka-http-core/src/main/scala/akka/http/model/parser/LinkHeader.scala index a40614c83a..ff42d5da34 100644 --- a/akka-http-core/src/main/scala/akka/http/model/parser/LinkHeader.scala +++ b/akka-http-core/src/main/scala/akka/http/model/parser/LinkHeader.scala @@ -22,14 +22,14 @@ private[parser] trait LinkHeader { this: Parser with CommonRules with CommonActi } def `link-param` = rule( - ws("rel") ~ ws('=') ~ `relation-types` ~> LinkParam.rel - | ws("anchor") ~ ws('=') ~ ws('"') ~ UriReference('"') ~ ws('"') ~> LinkParam.anchor - | ws("rev") ~ ws('=') ~ `relation-types` ~> LinkParam.rev - | ws("hreflang") ~ ws('=') ~ language ~> LinkParam.hreflang - | ws("media") ~ ws('=') ~ word ~> LinkParam.media - | ws("title") ~ ws('=') ~ word ~> LinkParam.title - | ws("title*") ~ ws('=') ~ word ~> LinkParam.`title*` // support full `ext-value` notation from http://tools.ietf.org/html/rfc5987#section-3.2.1 - | ws("type") ~ ws('=') ~ (ws('"') ~ `link-media-type` ~ ws('"') | `link-media-type`) ~> LinkParam.`type`) + ws("rel") ~ ws('=') ~ `relation-types` ~> LinkParams.rel + | ws("anchor") ~ ws('=') ~ ws('"') ~ UriReference('"') ~ ws('"') ~> LinkParams.anchor + | ws("rev") ~ ws('=') ~ `relation-types` ~> LinkParams.rev + | ws("hreflang") ~ ws('=') ~ language ~> LinkParams.hreflang + | ws("media") ~ ws('=') ~ word ~> LinkParams.media + | ws("title") ~ ws('=') ~ word ~> LinkParams.title + | ws("title*") ~ ws('=') ~ word ~> LinkParams.`title*` // support full `ext-value` notation from http://tools.ietf.org/html/rfc5987#section-3.2.1 + | ws("type") ~ ws('=') ~ (ws('"') ~ `link-media-type` ~ ws('"') | `link-media-type`) ~> LinkParams.`type`) // TODO: support `link-extension` def `relation-types` = rule( @@ -69,12 +69,12 @@ private[parser] trait LinkHeader { this: Parser with CommonRules with CommonActi seenMedia: Boolean = false, seenTitle: Boolean = false, seenTitleS: Boolean = false, seenType: Boolean = false): Seq[LinkParam] = params match { - case Seq((x: LinkParam.rel), tail @ _*) ⇒ sanitize(tail, if (seenRel) result else result :+ x, seenRel = true, seenMedia, seenTitle, seenTitleS, seenType) - case Seq((x: LinkParam.media), tail @ _*) ⇒ sanitize(tail, if (seenMedia) result else result :+ x, seenRel, seenMedia = true, seenTitle, seenTitleS, seenType) - case Seq((x: LinkParam.title), tail @ _*) ⇒ sanitize(tail, if (seenTitle) result else result :+ x, seenRel, seenMedia, seenTitle = true, seenTitleS, seenType) - case Seq((x: LinkParam.`title*`), tail @ _*) ⇒ sanitize(tail, if (seenTitleS) result else result :+ x, seenRel, seenMedia, seenTitle, seenTitleS = true, seenType) - case Seq((x: LinkParam.`type`), tail @ _*) ⇒ sanitize(tail, if (seenType) result else result :+ x, seenRel, seenMedia, seenTitle, seenTitleS, seenType = true) - case Seq(head, tail @ _*) ⇒ sanitize(tail, result :+ head, seenRel, seenMedia, seenTitle, seenTitleS, seenType) - case Nil ⇒ result + case Seq((x: LinkParams.rel), tail @ _*) ⇒ sanitize(tail, if (seenRel) result else result :+ x, seenRel = true, seenMedia, seenTitle, seenTitleS, seenType) + case Seq((x: LinkParams.media), tail @ _*) ⇒ sanitize(tail, if (seenMedia) result else result :+ x, seenRel, seenMedia = true, seenTitle, seenTitleS, seenType) + case Seq((x: LinkParams.title), tail @ _*) ⇒ sanitize(tail, if (seenTitle) result else result :+ x, seenRel, seenMedia, seenTitle = true, seenTitleS, seenType) + case Seq((x: LinkParams.`title*`), tail @ _*) ⇒ sanitize(tail, if (seenTitleS) result else result :+ x, seenRel, seenMedia, seenTitle, seenTitleS = true, seenType) + case Seq((x: LinkParams.`type`), tail @ _*) ⇒ sanitize(tail, if (seenType) result else result :+ x, seenRel, seenMedia, seenTitle, seenTitleS, seenType = true) + case Seq(head, tail @ _*) ⇒ sanitize(tail, result :+ head, seenRel, seenMedia, seenTitle, seenTitleS, seenType) + case Nil ⇒ result } } \ No newline at end of file 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 5a020bd4d0..790ccd5b55 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 @@ -88,8 +88,8 @@ class HttpHeaderSpec extends FreeSpec with Matchers { } "Accept-Ranges" in { - "Accept-Ranges: bytes" =!= `Accept-Ranges`(RangeUnit.Bytes) - "Accept-Ranges: bytes, sausages" =!= `Accept-Ranges`(RangeUnit.Bytes, RangeUnit.Other("sausages")) + "Accept-Ranges: bytes" =!= `Accept-Ranges`(RangeUnits.Bytes) + "Accept-Ranges: bytes, sausages" =!= `Accept-Ranges`(RangeUnits.Bytes, RangeUnits.Other("sausages")) "Accept-Ranges: none" =!= `Accept-Ranges`() } @@ -153,9 +153,9 @@ class HttpHeaderSpec extends FreeSpec with Matchers { } "Content-Disposition" in { - "Content-Disposition: form-data" =!= `Content-Disposition`(ContentDispositionType.`form-data`) + "Content-Disposition: form-data" =!= `Content-Disposition`(ContentDispositionTypes.`form-data`) "Content-Disposition: attachment; name=field1; filename=\"file/txt\"" =!= - `Content-Disposition`(ContentDispositionType.attachment, Map("name" -> "field1", "filename" -> "file/txt")) + `Content-Disposition`(ContentDispositionTypes.attachment, Map("name" -> "field1", "filename" -> "file/txt")) } "Content-Encoding" in { @@ -274,28 +274,28 @@ class HttpHeaderSpec extends FreeSpec with Matchers { } "Link" in { - "Link: ; rel=next" =!= Link(Uri("/?page=2"), LinkParam.next) - "Link: ; rel=next" =!= Link(Uri("https://spray.io"), LinkParam.next) + "Link: ; rel=next" =!= Link(Uri("/?page=2"), LinkParams.next) + "Link: ; rel=next" =!= Link(Uri("https://spray.io"), LinkParams.next) """Link: ; rel=prev, ; rel="next"""" =!= - Link(LinkValue(Uri("/"), LinkParam.prev), LinkValue(Uri("/page/2"), LinkParam.next)).renderedTo("; rel=prev, ; rel=next") + Link(LinkValue(Uri("/"), LinkParams.prev), LinkValue(Uri("/page/2"), LinkParams.next)).renderedTo("; rel=prev, ; rel=next") - """Link: ; rel="x.y-z http://spray.io"""" =!= Link(Uri("/"), LinkParam.rel("x.y-z http://spray.io")) - """Link: ; title="My Title"""" =!= Link(Uri("/"), LinkParam.title("My Title")) - """Link: ; rel=next; title="My Title"""" =!= Link(Uri("/"), LinkParam.next, LinkParam.title("My Title")) - """Link: ; anchor="http://example.com"""" =!= Link(Uri("/"), LinkParam.anchor(Uri("http://example.com"))) + """Link: ; rel="x.y-z http://spray.io"""" =!= Link(Uri("/"), LinkParams.rel("x.y-z http://spray.io")) + """Link: ; title="My Title"""" =!= Link(Uri("/"), LinkParams.title("My Title")) + """Link: ; rel=next; title="My Title"""" =!= Link(Uri("/"), LinkParams.next, LinkParams.title("My Title")) + """Link: ; anchor="http://example.com"""" =!= Link(Uri("/"), LinkParams.anchor(Uri("http://example.com"))) """Link: ; rev=foo; hreflang=de-de; media=print; type=application/json""" =!= - Link(Uri("/"), LinkParam.rev("foo"), LinkParam.hreflang(Language("de", "de")), LinkParam.media("print"), LinkParam.`type`(`application/json`)) + Link(Uri("/"), LinkParams.rev("foo"), LinkParams.hreflang(Language("de", "de")), LinkParams.media("print"), LinkParams.`type`(`application/json`)) /* RFC 5988 examples */ """Link: ; rel="previous"; title="previous chapter"""" =!= - Link(Uri("http://example.com/TheBook/chapter2"), LinkParam.rel("previous"), LinkParam.title("previous chapter")) + Link(Uri("http://example.com/TheBook/chapter2"), LinkParams.rel("previous"), LinkParams.title("previous chapter")) .renderedTo("""; rel=previous; title="previous chapter"""") - """Link: ; rel="http://example.net/foo"""" =!= Link(Uri("/"), LinkParam.rel("http://example.net/foo")) + """Link: ; rel="http://example.net/foo"""" =!= Link(Uri("/"), LinkParams.rel("http://example.net/foo")) .renderedTo("; rel=http://example.net/foo") """Link: ; rel="start http://example.net/relation/other"""" =!= Link(Uri("http://example.org/"), - LinkParam.rel("start http://example.net/relation/other")) + LinkParams.rel("start http://example.net/relation/other")) // only one 'rel=' is allowed, http://tools.ietf.org/html/rfc5988#section-5.3 requires any subsequent ones to be skipped "Link: ; rel=prev; rel=next" =!=> "; rel=prev" @@ -321,8 +321,8 @@ class HttpHeaderSpec extends FreeSpec with Matchers { } "Transfer-Encoding" in { - "Transfer-Encoding: chunked" =!= `Transfer-Encoding`(TransferEncoding.chunked) - "Transfer-Encoding: gzip" =!= `Transfer-Encoding`(TransferEncoding.gzip) + "Transfer-Encoding: chunked" =!= `Transfer-Encoding`(TransferEncodings.chunked) + "Transfer-Encoding: gzip" =!= `Transfer-Encoding`(TransferEncodings.gzip) } "Range" in { diff --git a/akka-http-core/src/test/scala/akka/http/parsing/RequestParserSpec.scala b/akka-http-core/src/test/scala/akka/http/parsing/RequestParserSpec.scala index 645b9c09c1..9f3bb33ad9 100644 --- a/akka-http-core/src/test/scala/akka/http/parsing/RequestParserSpec.scala +++ b/akka-http-core/src/test/scala/akka/http/parsing/RequestParserSpec.scala @@ -157,7 +157,7 @@ class RequestParserSpec extends FreeSpec with Matchers with BeforeAndAfterAll { | |""" val baseRequest = HttpRequest(PATCH, "/data", List(Host("ping"), `Content-Type`(`application/pdf`), - Connection("lalelu"), `Transfer-Encoding`(TransferEncoding.chunked))) + Connection("lalelu"), `Transfer-Encoding`(TransferEncodings.chunked))) "request start" in new Test { Seq(start, "rest") should generalMultiParseTo( @@ -220,7 +220,7 @@ class RequestParserSpec extends FreeSpec with Matchers with BeforeAndAfterAll { | |""" val baseRequest = HttpRequest(PATCH, "/data", List(Host("ping"), Connection("lalelu"), - `Transfer-Encoding`(TransferEncoding.chunked)), HttpEntity.Chunked(`application/octet-stream`, producer())) + `Transfer-Encoding`(TransferEncodings.chunked)), HttpEntity.Chunked(`application/octet-stream`, producer())) "an illegal char after chunk size" in new Test { Seq(start, From fe7a0909936ec03eaf87c9686c7696525a390f44 Mon Sep 17 00:00:00 2001 From: Johannes Rudolph Date: Wed, 11 Jun 2014 10:03:08 +0200 Subject: [PATCH 6/8] !hco get rid of extra HttpResponse constructor which changes ordering The problem is that those extra constructors can easily lead to ambiguities in overloading resolution. We should rely on named parameters to get a similar effect than those extra constructors. --- .../src/main/scala/akka/http/model/HttpMessage.scala | 4 ---- .../main/scala/akka/http/server/HttpServerPipeline.scala | 2 +- akka-http-core/src/test/scala/akka/http/TestServer.scala | 4 ++-- .../scala/akka/http/rendering/ResponseRendererSpec.scala | 8 ++++---- 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/akka-http-core/src/main/scala/akka/http/model/HttpMessage.scala b/akka-http-core/src/main/scala/akka/http/model/HttpMessage.scala index 04528f9969..338db0a538 100644 --- a/akka-http-core/src/main/scala/akka/http/model/HttpMessage.scala +++ b/akka-http-core/src/main/scala/akka/http/model/HttpMessage.scala @@ -282,8 +282,4 @@ final case class HttpResponse(status: StatusCode = StatusCodes.OK, def withHeadersAndEntity(headers: immutable.Seq[HttpHeader], entity: HttpEntity) = if ((headers eq this.headers) && (entity eq this.entity)) this else copy(headers = headers, entity = entity) -} - -object HttpResponse { - def apply(status: StatusCode, entity: HttpEntity): HttpResponse = HttpResponse(status, Nil, entity) } \ No newline at end of file diff --git a/akka-http-core/src/main/scala/akka/http/server/HttpServerPipeline.scala b/akka-http-core/src/main/scala/akka/http/server/HttpServerPipeline.scala index b84a111626..340bcedb5d 100644 --- a/akka-http-core/src/main/scala/akka/http/server/HttpServerPipeline.scala +++ b/akka-http-core/src/main/scala/akka/http/server/HttpServerPipeline.scala @@ -106,7 +106,7 @@ private[http] class HttpServerPipeline(settings: ServerSettings, def errorResponse(status: StatusCode, info: ErrorInfo): ResponseRenderingContext = { log.warning("Illegal request, responding with status '{}': {}", status, info.formatPretty) val msg = if (settings.verboseErrorMessages) info.formatPretty else info.summary - ResponseRenderingContext(HttpResponse(status, msg), closeAfterResponseCompletion = true) + ResponseRenderingContext(HttpResponse(status, entity = msg), closeAfterResponseCompletion = true) } } } diff --git a/akka-http-core/src/test/scala/akka/http/TestServer.scala b/akka-http-core/src/test/scala/akka/http/TestServer.scala index 7c6a8627c6..21da0c552d 100644 --- a/akka-http-core/src/test/scala/akka/http/TestServer.scala +++ b/akka-http-core/src/test/scala/akka/http/TestServer.scala @@ -27,7 +27,7 @@ object TestServer extends App { case HttpRequest(GET, Uri.Path("/"), _, _, _) ⇒ index case HttpRequest(GET, Uri.Path("/ping"), _, _, _) ⇒ HttpResponse(entity = "PONG!") case HttpRequest(GET, Uri.Path("/crash"), _, _, _) ⇒ sys.error("BOOM!") - case _: HttpRequest ⇒ HttpResponse(404, "Unknown resource!") + case _: HttpRequest ⇒ HttpResponse(404, entity = "Unknown resource!") } val materializer = FlowMaterializer(MaterializerSettings()) @@ -62,4 +62,4 @@ object TestServer extends App { .toString())) -} \ No newline at end of file +} diff --git a/akka-http-core/src/test/scala/akka/http/rendering/ResponseRendererSpec.scala b/akka-http-core/src/test/scala/akka/http/rendering/ResponseRendererSpec.scala index 2cf0c509d7..d7dbda3e9a 100644 --- a/akka-http-core/src/test/scala/akka/http/rendering/ResponseRendererSpec.scala +++ b/akka-http-core/src/test/scala/akka/http/rendering/ResponseRendererSpec.scala @@ -126,14 +126,14 @@ class ResponseRendererSpec extends FreeSpec with Matchers with BeforeAndAfterAll } "with one chunk and incorrect (too large) Content-Length" in new TestSetup() { the[RuntimeException] thrownBy { - HttpResponse(200, Default(ContentTypes.`application/json`, 10, + HttpResponse(200, entity = Default(ContentTypes.`application/json`, 10, producer(ByteString("body123")))) should renderTo("") } should have message "Response had declared Content-Length 10 but entity chunk stream amounts to 3 bytes less" } "with one chunk and incorrect (too small) Content-Length" in new TestSetup() { the[RuntimeException] thrownBy { - HttpResponse(200, Default(ContentTypes.`application/json`, 5, + HttpResponse(200, entity = Default(ContentTypes.`application/json`, 5, producer(ByteString("body123")))) should renderTo("") } should have message "Response had declared Content-Length 5 but entity chunk stream amounts to more bytes" } @@ -142,7 +142,7 @@ class ResponseRendererSpec extends FreeSpec with Matchers with BeforeAndAfterAll "a response with a CloseDelimited body" - { "without data" in new TestSetup() { ResponseRenderingContext( - HttpResponse(200, CloseDelimited(ContentTypes.`application/json`, + HttpResponse(200, entity = CloseDelimited(ContentTypes.`application/json`, producer(ByteString.empty)))) should renderTo( """HTTP/1.1 200 OK |Server: akka-http/1.0.0 @@ -154,7 +154,7 @@ class ResponseRendererSpec extends FreeSpec with Matchers with BeforeAndAfterAll } "consisting of two parts" in new TestSetup() { ResponseRenderingContext( - HttpResponse(200, CloseDelimited(ContentTypes.`application/json`, + HttpResponse(200, entity = CloseDelimited(ContentTypes.`application/json`, producer(ByteString("abc"), ByteString("defg"))))) should renderTo( """HTTP/1.1 200 OK |Server: akka-http/1.0.0 From 6908a72681ba2b614e25ede3c2245ea8c678869d Mon Sep 17 00:00:00 2001 From: Johannes Rudolph Date: Thu, 12 Jun 2014 16:11:02 +0200 Subject: [PATCH 7/8] !hco #15392 use `params` instead of `parameters` consistently --- .../scala/akka/http/model/MediaType.scala | 88 +++++++++---------- .../akka/http/model/MultipartContent.scala | 4 +- .../akka/http/model/headers/headers.scala | 4 +- .../akka/http/model/parser/AcceptHeader.scala | 2 +- .../http/model/parser/CommonActions.scala | 20 ++--- .../http/model/parser/HttpHeaderSpec.scala | 6 +- 6 files changed, 62 insertions(+), 62 deletions(-) diff --git a/akka-http-core/src/main/scala/akka/http/model/MediaType.scala b/akka-http-core/src/main/scala/akka/http/model/MediaType.scala index ae3a7e219a..d5f8d4a0f7 100644 --- a/akka-http-core/src/main/scala/akka/http/model/MediaType.scala +++ b/akka-http-core/src/main/scala/akka/http/model/MediaType.scala @@ -11,7 +11,7 @@ import akka.http.util._ sealed abstract class MediaRange extends Renderable with WithQValue[MediaRange] { def value: String def mainType: String - def parameters: Map[String, String] + def params: Map[String, String] def qValue: Float def matches(mediaType: MediaType): Boolean def isApplication = false @@ -23,10 +23,10 @@ sealed abstract class MediaRange extends Renderable with WithQValue[MediaRange] def isVideo = false /** - * Returns a copy of this instance with the parameters replaced by the given ones. + * Returns a copy of this instance with the params replaced by the given ones. * If the given map contains a "q" value the `qValue` member is (also) updated. */ - def withParameters(parameters: Map[String, String]): MediaRange + def withParams(params: Map[String, String]): MediaRange /** * Constructs a `ContentTypeRange` from this instance and the given charset. @@ -35,22 +35,22 @@ sealed abstract class MediaRange extends Renderable with WithQValue[MediaRange] } object MediaRange { - private[http] def splitOffQValue(parameters: Map[String, String], defaultQ: Float = 1.0f): (Map[String, String], Float) = - parameters.get("q") match { - case Some(x) ⇒ (parameters - "q") -> (try x.toFloat catch { case _: NumberFormatException ⇒ 1.0f }) - case None ⇒ parameters -> defaultQ + private[http] def splitOffQValue(params: Map[String, String], defaultQ: Float = 1.0f): (Map[String, String], Float) = + params.get("q") match { + case Some(x) ⇒ (params - "q") -> (try x.toFloat catch { case _: NumberFormatException ⇒ 1.0f }) + case None ⇒ params -> defaultQ } - private final case class Custom(mainType: String, parameters: Map[String, String], qValue: Float) + private final case class Custom(mainType: String, params: Map[String, String], qValue: Float) extends MediaRange with ValueRenderable { require(0.0f <= qValue && qValue <= 1.0f, "qValue must be >= 0 and <= 1.0") def matches(mediaType: MediaType) = mainType == "*" || mediaType.mainType == mainType - def withParameters(parameters: Map[String, String]) = custom(mainType, parameters, qValue) - def withQValue(qValue: Float) = if (qValue != this.qValue) custom(mainType, parameters, qValue) else this + def withParams(params: Map[String, String]) = custom(mainType, params, qValue) + def withQValue(qValue: Float) = if (qValue != this.qValue) custom(mainType, params, qValue) else this def render[R <: Rendering](r: R): r.type = { r ~~ mainType ~~ '/' ~~ '*' if (qValue < 1.0f) r ~~ ";q=" ~~ qValue - if (parameters.nonEmpty) parameters foreach { case (k, v) ⇒ r ~~ ';' ~~ ' ' ~~ k ~~ '=' ~~# v } + if (params.nonEmpty) params foreach { case (k, v) ⇒ r ~~ ';' ~~ ' ' ~~ k ~~ '=' ~~# v } r } override def isApplication = mainType == "application" @@ -62,15 +62,15 @@ object MediaRange { override def isVideo = mainType == "video" } - def custom(mainType: String, parameters: Map[String, String] = Map.empty, qValue: Float = 1.0f): MediaRange = { - val (params, q) = splitOffQValue(parameters, qValue) - Custom(mainType.toLowerCase, params, q) + def custom(mainType: String, params: Map[String, String] = Map.empty, qValue: Float = 1.0f): MediaRange = { + val (ps, q) = splitOffQValue(params, qValue) + Custom(mainType.toLowerCase, ps, q) } final case class One(mediaType: MediaType, qValue: Float) extends MediaRange with ValueRenderable { require(0.0f <= qValue && qValue <= 1.0f, "qValue must be >= 0 and <= 1.0") def mainType = mediaType.mainType - def parameters = mediaType.parameters + def params = mediaType.params override def isApplication = mediaType.isApplication override def isAudio = mediaType.isAudio override def isImage = mediaType.isImage @@ -80,7 +80,7 @@ object MediaRange { override def isVideo = mediaType.isVideo def matches(mediaType: MediaType) = this.mediaType.mainType == mediaType.mainType && this.mediaType.subType == mediaType.subType - def withParameters(parameters: Map[String, String]) = copy(mediaType = mediaType.withParameters(parameters)) + def withParams(params: Map[String, String]) = copy(mediaType = mediaType.withParams(params)) def withQValue(qValue: Float) = copy(qValue = qValue) def render[R <: Rendering](r: R): r.type = if (qValue < 1.0f) r ~~ mediaType ~~ ";q=" ~~ qValue else r ~~ mediaType } @@ -94,10 +94,10 @@ object MediaRanges extends ObjectRegistry[String, MediaRange] { sealed abstract case class PredefinedMediaRange(value: String) extends MediaRange with LazyValueBytesRenderable { val mainType = value takeWhile (_ != '/') register(mainType, this) - def parameters = Map.empty + def params = Map.empty def qValue = 1.0f - def withParameters(parameters: Map[String, String]) = MediaRange.custom(mainType, parameters) - def withQValue(qValue: Float) = if (qValue != 1.0f) MediaRange.custom(mainType, parameters, qValue) else this + def withParams(params: Map[String, String]) = MediaRange.custom(mainType, params) + def withQValue(qValue: Float) = if (qValue != 1.0f) MediaRange.custom(mainType, params, qValue) else this } val `*/*` = new PredefinedMediaRange("*/*") { @@ -138,7 +138,7 @@ sealed abstract case class MediaType private[http] (value: String)(val mainType: val compressible: Boolean, val binary: Boolean, val fileExtensions: immutable.Seq[String], - val parameters: Map[String, String]) + val params: Map[String, String]) extends LazyValueBytesRenderable with WithQValue[MediaRange] { def isApplication = false def isAudio = false @@ -149,9 +149,9 @@ sealed abstract case class MediaType private[http] (value: String)(val mainType: def isVideo = false /** - * Returns a copy of this instance with the parameters replaced by the given ones. + * Returns a copy of this instance with the params replaced by the given ones. */ - def withParameters(parameters: Map[String, String]): MediaType + def withParams(params: Map[String, String]): MediaType /** * Constructs a `ContentType` from this instance and the given charset. @@ -161,24 +161,24 @@ sealed abstract case class MediaType private[http] (value: String)(val mainType: def withQValue(qValue: Float): MediaRange = MediaRange(this, qValue.toFloat) } -class MultipartMediaType private[http] (_value: String, _subType: String, _parameters: Map[String, String]) - extends MediaType(_value)("multipart", _subType, compressible = true, binary = true, Nil, _parameters) { +class MultipartMediaType private[http] (_value: String, _subType: String, _params: Map[String, String]) + extends MediaType(_value)("multipart", _subType, compressible = true, binary = true, Nil, _params) { override def isMultipart = true - def withBoundary(boundary: String): MultipartMediaType = withParameters { - if (boundary.isEmpty) parameters - "boundary" else parameters.updated("boundary", boundary) + def withBoundary(boundary: String): MultipartMediaType = withParams { + if (boundary.isEmpty) params - "boundary" else params.updated("boundary", boundary) } - def withParameters(parameters: Map[String, String]) = MediaTypes.multipart(subType, parameters) + def withParams(params: Map[String, String]) = MediaTypes.multipart(subType, params) } sealed abstract class NonMultipartMediaType private[http] (_value: String, _mainType: String, _subType: String, _compressible: Boolean, _binary: Boolean, _fileExtensions: immutable.Seq[String], - _parameters: Map[String, String]) - extends MediaType(_value)(_mainType, _subType, _compressible, _binary, _fileExtensions, _parameters) { + _params: Map[String, String]) + extends MediaType(_value)(_mainType, _subType, _compressible, _binary, _fileExtensions, _params) { private[http] def this(mainType: String, subType: String, compressible: Boolean, binary: Boolean, fileExtensions: immutable.Seq[String]) = this(mainType + '/' + subType, mainType, subType, compressible, binary, fileExtensions, Map.empty) - def withParameters(parameters: Map[String, String]) = - MediaType.custom(mainType, subType, compressible, binary, fileExtensions, parameters) + def withParams(params: Map[String, String]) = + MediaType.custom(mainType, subType, compressible, binary, fileExtensions, params) } object MediaType { @@ -188,13 +188,13 @@ object MediaType { * your custom Marshallers and Unmarshallers. */ def custom(mainType: String, subType: String, compressible: Boolean = false, binary: Boolean = false, - fileExtensions: immutable.Seq[String] = Nil, parameters: Map[String, String] = Map.empty, + fileExtensions: immutable.Seq[String] = Nil, params: Map[String, String] = Map.empty, allowArbitrarySubtypes: Boolean = false): MediaType = { require(mainType != "multipart", "Cannot create a MultipartMediaType here, use `multipart.apply` instead!") require(allowArbitrarySubtypes || subType != "*", "Cannot create a MediaRange here, use `MediaRange.custom` instead!") val r = new StringRendering ~~ mainType ~~ '/' ~~ subType - if (parameters.nonEmpty) parameters foreach { case (k, v) ⇒ r ~~ ';' ~~ ' ' ~~ k ~~ '=' ~~# v } - new NonMultipartMediaType(r.get, mainType, subType, compressible, binary, fileExtensions, parameters) { + if (params.nonEmpty) params foreach { case (k, v) ⇒ r ~~ ';' ~~ ' ' ~~ k ~~ '=' ~~# v } + new NonMultipartMediaType(r.get, mainType, subType, compressible, binary, fileExtensions, params) { override def isApplication = mainType == "application" override def isAudio = mainType == "audio" override def isImage = mainType == "image" @@ -374,19 +374,19 @@ object MediaTypes extends ObjectRegistry[(String, String), MediaType] { val `message/rfc822` = msg("rfc822", "eml", "mht", "mhtml", "mime") object multipart { - def apply(subType: String, parameters: Map[String, String]): MultipartMediaType = { + def apply(subType: String, params: Map[String, String]): MultipartMediaType = { require(subType != "*", "Cannot create a MediaRange here, use MediaRanges.`multipart/*` instead!") val r = new StringRendering ~~ "multipart/" ~~ subType - if (parameters.nonEmpty) parameters foreach { case (k, v) ⇒ r ~~ ';' ~~ ' ' ~~ k ~~ '=' ~~# v } - new MultipartMediaType(r.get, subType, parameters) + if (params.nonEmpty) params foreach { case (k, v) ⇒ r ~~ ';' ~~ ' ' ~~ k ~~ '=' ~~# v } + new MultipartMediaType(r.get, subType, params) } - def mixed (parameters: Map[String, String]) = apply("mixed", parameters) - def alternative(parameters: Map[String, String]) = apply("alternative", parameters) - def related (parameters: Map[String, String]) = apply("related", parameters) - def `form-data`(parameters: Map[String, String]) = apply("form-data", parameters) - def signed (parameters: Map[String, String]) = apply("signed", parameters) - def encrypted (parameters: Map[String, String]) = apply("encrypted", parameters) - def byteRanges (parameters: Map[String, String]) = apply("byteranges", parameters) + def mixed (params: Map[String, String]) = apply("mixed", params) + def alternative(params: Map[String, String]) = apply("alternative", params) + def related (params: Map[String, String]) = apply("related", params) + def `form-data`(params: Map[String, String]) = apply("form-data", params) + def signed (params: Map[String, String]) = apply("signed", params) + def encrypted (params: Map[String, String]) = apply("encrypted", params) + def byteRanges (params: Map[String, String]) = apply("byteranges", params) } val `multipart/mixed` = multipart.mixed(Map.empty) diff --git a/akka-http-core/src/main/scala/akka/http/model/MultipartContent.scala b/akka-http-core/src/main/scala/akka/http/model/MultipartContent.scala index 61c076000a..addf5864e4 100644 --- a/akka-http-core/src/main/scala/akka/http/model/MultipartContent.scala +++ b/akka-http-core/src/main/scala/akka/http/model/MultipartContent.scala @@ -77,6 +77,6 @@ object BodyPart { } def apply(entity: HttpEntity, fieldName: String): BodyPart = apply(entity, fieldName, Map.empty[String, String]) - def apply(entity: HttpEntity, fieldName: String, parameters: Map[String, String]): BodyPart = - BodyPart(entity, immutable.Seq(`Content-Disposition`(ContentDispositionTypes.`form-data`, parameters.updated("name", fieldName)))) + def apply(entity: HttpEntity, fieldName: String, params: Map[String, String]): BodyPart = + BodyPart(entity, immutable.Seq(`Content-Disposition`(ContentDispositionTypes.`form-data`, params.updated("name", fieldName)))) } \ No newline at end of file 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 72b7454675..4bd761b251 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 @@ -215,10 +215,10 @@ final case class Connection(tokens: immutable.Seq[String]) extends ModeledHeader // http://tools.ietf.org/html/rfc6266 object `Content-Disposition` extends ModeledCompanion -final case class `Content-Disposition`(dispositionType: ContentDispositionType, parameters: Map[String, String] = Map.empty) extends ModeledHeader { +final case class `Content-Disposition`(dispositionType: ContentDispositionType, params: Map[String, String] = Map.empty) extends ModeledHeader { protected def renderValue[R <: Rendering](r: R): r.type = { r ~~ dispositionType - parameters foreach { case (k, v) ⇒ r ~~ "; " ~~ k ~~ '=' ~~# v } + params foreach { case (k, v) ⇒ r ~~ "; " ~~ k ~~ '=' ~~# v } r } protected def companion = `Content-Disposition` diff --git a/akka-http-core/src/main/scala/akka/http/model/parser/AcceptHeader.scala b/akka-http-core/src/main/scala/akka/http/model/parser/AcceptHeader.scala index 611f444ee0..f62012f875 100644 --- a/akka-http-core/src/main/scala/akka/http/model/parser/AcceptHeader.scala +++ b/akka-http-core/src/main/scala/akka/http/model/parser/AcceptHeader.scala @@ -22,7 +22,7 @@ private[parser] trait AcceptHeader { this: Parser with CommonRules with CommonAc if (sub == "*") { val mainLower = main.toLowerCase MediaRanges.getForKey(mainLower) match { - case Some(registered) ⇒ if (params.isEmpty) registered else registered.withParameters(params.toMap) + case Some(registered) ⇒ if (params.isEmpty) registered else registered.withParams(params.toMap) case None ⇒ MediaRange.custom(mainLower, params.toMap) } } else { diff --git a/akka-http-core/src/main/scala/akka/http/model/parser/CommonActions.scala b/akka-http-core/src/main/scala/akka/http/model/parser/CommonActions.scala index 39b0e17d67..5d9053d490 100644 --- a/akka-http-core/src/main/scala/akka/http/model/parser/CommonActions.scala +++ b/akka-http-core/src/main/scala/akka/http/model/parser/CommonActions.scala @@ -11,21 +11,21 @@ private[parser] trait CommonActions { type StringMapBuilder = scala.collection.mutable.Builder[(String, String), Map[String, String]] - def getMediaType(mainType: String, subType: String, parameters: Map[String, String]): MediaType = { + def getMediaType(mainType: String, subType: String, params: Map[String, String]): MediaType = { mainType.toLowerCase match { case "multipart" ⇒ subType.toLowerCase match { - case "mixed" ⇒ multipart.mixed(parameters) - case "alternative" ⇒ multipart.alternative(parameters) - case "related" ⇒ multipart.related(parameters) - case "form-data" ⇒ multipart.`form-data`(parameters) - case "signed" ⇒ multipart.signed(parameters) - case "encrypted" ⇒ multipart.encrypted(parameters) - case custom ⇒ multipart(custom, parameters) + case "mixed" ⇒ multipart.mixed(params) + case "alternative" ⇒ multipart.alternative(params) + case "related" ⇒ multipart.related(params) + case "form-data" ⇒ multipart.`form-data`(params) + case "signed" ⇒ multipart.signed(params) + case "encrypted" ⇒ multipart.encrypted(params) + case custom ⇒ multipart(custom, params) } case mainLower ⇒ MediaTypes.getForKey((mainLower, subType.toLowerCase)) match { - case Some(registered) ⇒ if (parameters.isEmpty) registered else registered.withParameters(parameters) - case None ⇒ MediaType.custom(mainType, subType, parameters = parameters, allowArbitrarySubtypes = true) + case Some(registered) ⇒ if (params.isEmpty) registered else registered.withParams(params) + case None ⇒ MediaType.custom(mainType, subType, params = params, allowArbitrarySubtypes = true) } } } 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 790ccd5b55..fd529767da 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 @@ -35,9 +35,9 @@ class HttpHeaderSpec extends FreeSpec with Matchers { "Accept: */*, text/*; foo=bar, custom/custom; bar=\"b>az\"" =!= Accept(`*/*`, MediaRange.custom("text", Map("foo" -> "bar")), - MediaType.custom("custom", "custom", parameters = Map("bar" -> "b>az"))) + MediaType.custom("custom", "custom", params = Map("bar" -> "b>az"))) "Accept: application/*+xml; version=2" =!= - Accept(MediaType.custom("application", "*+xml", parameters = Map("version" -> "2"))) + Accept(MediaType.custom("application", "*+xml", params = Map("version" -> "2"))) } "Accept-Charset" in { @@ -175,7 +175,7 @@ class HttpHeaderSpec extends FreeSpec with Matchers { "Content-Type: text/plain; charset=utf8" =!= `Content-Type`(ContentType(`text/plain`, `UTF-8`)).renderedTo("text/plain; charset=UTF-8") "Content-Type: text/xml; version=3; charset=windows-1252" =!= - `Content-Type`(ContentType(MediaType.custom("text", "xml", parameters = Map("version" -> "3")), HttpCharsets.getForKey("windows-1252"))) + `Content-Type`(ContentType(MediaType.custom("text", "xml", params = Map("version" -> "3")), HttpCharsets.getForKey("windows-1252"))) "Content-Type: text/plain; charset=fancy-pants" =!= ErrorInfo("Illegal HTTP header 'Content-Type': Unsupported charset", "fancy-pants") "Content-Type: multipart/mixed; boundary=ABC123" =!= From 4d79d0d88e2f115ee6f10e0de12545270a618176 Mon Sep 17 00:00:00 2001 From: Johannes Rudolph Date: Thu, 12 Jun 2014 16:20:00 +0200 Subject: [PATCH 8/8] !hco privatize some internal Uri.Host constructors --- .../src/main/scala/akka/http/model/Uri.scala | 24 +++++++++---- .../test/scala/akka/http/model/UriSpec.scala | 34 +++++++++++++++++++ 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/akka-http-core/src/main/scala/akka/http/model/Uri.scala b/akka-http-core/src/main/scala/akka/http/model/Uri.scala index d38190b25b..c4f6081c2f 100644 --- a/akka-http-core/src/main/scala/akka/http/model/Uri.scala +++ b/akka-http-core/src/main/scala/akka/http/model/Uri.scala @@ -15,7 +15,7 @@ import akka.http.model.parser.UriParser import akka.http.model.parser.CharacterClasses._ import akka.http.util._ import Uri._ -import java.net.InetAddress +import java.net.{ Inet4Address, Inet6Address, InetAddress } /** * An immutable model of an internet URI as defined by http://tools.ietf.org/html/rfc3986. @@ -324,12 +324,20 @@ object Uri { } def apply(string: String, charset: Charset = UTF8, mode: Uri.ParsingMode = Uri.ParsingMode.Relaxed): Host = if (!string.isEmpty) new UriParser(string, UTF8, mode).parseHost() else Empty + + def apply(address: InetAddress): Host = address match { + case ipv4: Inet4Address ⇒ apply(ipv4) + case ipv6: Inet6Address ⇒ apply(ipv6) + case _ ⇒ throw new IllegalArgumentException(s"Unexpected address type(${address.getClass.getSimpleName}): $address") + } + def apply(address: Inet4Address): IPv4Host = IPv4Host(address.getAddress, address.getHostAddress) + def apply(address: Inet6Address): IPv6Host = IPv6Host(address.getAddress, address.getHostAddress) } sealed abstract class NonEmptyHost extends Host { def isEmpty = false def toOption = Some(this) } - final case class IPv4Host(bytes: immutable.Seq[Byte], address: String) extends NonEmptyHost { + final case class IPv4Host private[http] (bytes: immutable.Seq[Byte], address: String) extends NonEmptyHost { require(bytes.length == 4, "bytes array must have length 4") require(!address.isEmpty, "address must not be empty") def equalsIgnoreCase(other: Host): Boolean = other match { @@ -343,9 +351,10 @@ object Uri { def apply(address: String): IPv4Host = apply(address.split('.').map(_.toInt.toByte)) def apply(byte1: Byte, byte2: Byte, byte3: Byte, byte4: Byte): IPv4Host = apply(Array(byte1, byte2, byte3, byte4)) def apply(bytes: Array[Byte]): IPv4Host = apply(bytes, bytes.map(_ & 0xFF).mkString(".")) - def apply(bytes: Array[Byte], address: String): IPv4Host = apply(immutable.Seq(bytes: _*), address) + + private[http] def apply(bytes: Array[Byte], address: String): IPv4Host = IPv4Host(immutable.Seq(bytes: _*), address) } - final case class IPv6Host(bytes: immutable.Seq[Byte], address: String) extends NonEmptyHost { + final case class IPv6Host private (bytes: immutable.Seq[Byte], address: String) extends NonEmptyHost { require(bytes.length == 16, "bytes array must have length 16") require(!address.isEmpty, "address must not be empty") def equalsIgnoreCase(other: Host): Boolean = other match { @@ -356,12 +365,15 @@ object Uri { def inetAddresses = immutable.Seq(InetAddress.getByAddress(bytes.toArray)) } object IPv6Host { - def apply(bytes: String, address: String): IPv6Host = { + def apply(bytes: Array[Byte]): IPv6Host = Host(InetAddress.getByAddress(bytes).asInstanceOf[Inet6Address]) + def apply(bytes: immutable.Seq[Byte]): IPv6Host = apply(bytes.toArray) + + private[http] def apply(bytes: String, address: String): IPv6Host = { import CharUtils.{ hexValue ⇒ hex } require(bytes.length == 32, "`bytes` must be a 32 character hex string") apply(bytes.toCharArray.grouped(2).map(s ⇒ (hex(s(0)) * 16 + hex(s(1))).toByte).toArray, address) } - def apply(bytes: Array[Byte], address: String): IPv6Host = apply(immutable.Seq(bytes: _*), address) + private[http] def apply(bytes: Array[Byte], address: String): IPv6Host = apply(immutable.Seq(bytes: _*), address) } final case class NamedHost(address: String) extends NonEmptyHost { def equalsIgnoreCase(other: Host): Boolean = other match { diff --git a/akka-http-core/src/test/scala/akka/http/model/UriSpec.scala b/akka-http-core/src/test/scala/akka/http/model/UriSpec.scala index 8f7f3422a9..d9cb8e7035 100644 --- a/akka-http-core/src/test/scala/akka/http/model/UriSpec.scala +++ b/akka-http-core/src/test/scala/akka/http/model/UriSpec.scala @@ -7,6 +7,7 @@ package akka.http.model import org.scalatest.{ Matchers, WordSpec } import akka.parboiled2.UTF8 import Uri._ +import java.net.InetAddress class UriSpec extends WordSpec with Matchers { @@ -21,6 +22,23 @@ class UriSpec extends WordSpec with Matchers { Host("3.0.0.0") shouldEqual IPv4Host("3.0.0.0") Host("30.0.0.0") shouldEqual IPv4Host("30.0.0.0") } + "support inetAddresses round-trip for Inet4Addresses" in { + def roundTrip(ip: String): Unit = { + val inetAddr = InetAddress.getByName(ip) + val addr = Host(inetAddr) + addr shouldEqual IPv4Host(ip) + addr.inetAddresses shouldEqual Seq(inetAddr) + } + + roundTrip("192.0.2.16") + roundTrip("192.0.2.16") + roundTrip("255.0.0.0") + roundTrip("0.0.0.0") + roundTrip("1.0.0.0") + roundTrip("2.0.0.0") + roundTrip("3.0.0.0") + roundTrip("30.0.0.0") + } "parse correctly from IPv6 literals (RFC2732)" in { // various @@ -79,6 +97,22 @@ class UriSpec extends WordSpec with Matchers { Host("[a:b:c::12:1]") shouldEqual IPv6Host("000a000b000c00000000000000120001", "a:b:c::12:1") Host("[a:b::0:1:2:3]") shouldEqual IPv6Host("000a000b000000000000000100020003", "a:b::0:1:2:3") } + "support inetAddresses round-trip for Inet6Addresses" in { + def fromAddress(address: String): IPv6Host = Host(s"[$address]").asInstanceOf[IPv6Host] + def roundTrip(ip: String): Unit = { + val inetAddr = InetAddress.getByName(ip) + val addr = Host(inetAddr) + addr equalsIgnoreCase fromAddress(ip) should be(true) + addr.inetAddresses shouldEqual Seq(inetAddr) + } + + roundTrip("1:1:1::1:1:1:1") + roundTrip("::1:2:3:4:5:6:7") + roundTrip("2001:0DB8:0100:F101:0210:A4FF:FEE3:9566") + roundTrip("2001:0db8:100:f101:0:0:0:1") + roundTrip("abcd::12") + roundTrip("::192.9.5.5") + } "parse correctly from NamedHost literals" in { Host("www.spray.io") shouldEqual NamedHost("www.spray.io")