Merge pull request #15391 from spray/w/small-http-core-changes

+hco Small akka-http-core changes as a preparation for the Java API
This commit is contained in:
Björn Antonsson 2014-06-13 09:17:20 +02:00
commit 0b45bb8e47
26 changed files with 232 additions and 159 deletions

View file

@ -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

View file

@ -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): _*
}
}

View file

@ -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)
}

View file

@ -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)

View file

@ -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))))
}

View file

@ -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 {

View file

@ -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
}

View file

@ -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,

View file

@ -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()

View file

@ -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

View file

@ -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"
}

View file

@ -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

View file

@ -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`
}

View file

@ -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 {

View file

@ -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)
}
}
}

View file

@ -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 ~> (_ -> _) }

View file

@ -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 }

View file

@ -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
}
}

View file

@ -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)
}
}
}

View file

@ -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)

View file

@ -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 {
</ul>
</body>
</html>.toString()))
}
}

View file

@ -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")

View file

@ -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: </?page=2>; rel=next" =!= Link(Uri("/?page=2"), LinkParam.next)
"Link: <https://spray.io>; rel=next" =!= Link(Uri("https://spray.io"), LinkParam.next)
"Link: </?page=2>; rel=next" =!= Link(Uri("/?page=2"), LinkParams.next)
"Link: <https://spray.io>; rel=next" =!= Link(Uri("https://spray.io"), LinkParams.next)
"""Link: </>; rel=prev, </page/2>; rel="next"""" =!=
Link(LinkValue(Uri("/"), LinkParam.prev), LinkValue(Uri("/page/2"), LinkParam.next)).renderedTo("</>; rel=prev, </page/2>; rel=next")
Link(LinkValue(Uri("/"), LinkParams.prev), LinkValue(Uri("/page/2"), LinkParams.next)).renderedTo("</>; rel=prev, </page/2>; 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: <http://example.com/TheBook/chapter2>; 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("""<http://example.com/TheBook/chapter2>; 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: <http://example.org/>; 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 {

View file

@ -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,

View file

@ -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

View file

@ -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 {