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 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/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/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 4555754896..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 @@ -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) } @@ -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`(ContentDispositionType.`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/Uri.scala b/akka-http-core/src/main/scala/akka/http/model/Uri.scala index 04736ecd4d..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,6 +15,7 @@ import akka.http.model.parser.UriParser import akka.http.model.parser.CharacterClasses._ import akka.http.util._ import Uri._ +import java.net.{ Inet4Address, Inet6Address, 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,50 +318,70 @@ 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 = 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 { 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)) 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 { case IPv6Host(`bytes`, _) ⇒ true case _ ⇒ false } + + 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 { case NamedHost(otherAddress) ⇒ address equalsIgnoreCase otherAddress case _ ⇒ false } + + def inetAddresses = InetAddress.getAllByName(address).toList } sealed abstract class Path { 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/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, 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..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` @@ -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/AcceptHeader.scala b/akka-http-core/src/main/scala/akka/http/model/parser/AcceptHeader.scala index fbab643449..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 @@ -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 { @@ -21,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/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/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/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-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/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") 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..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 { @@ -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 { @@ -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" =!= @@ -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, 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 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 {