Merge pull request #15967 from spray/w/15635-http-remove-all-global-state

!hco #15635 reorganize registration of predefined, custom HTTP primitives + other small improvements
This commit is contained in:
Björn Antonsson 2014-09-25 15:12:03 +02:00
commit fb96a2964b
23 changed files with 128 additions and 105 deletions

View file

@ -6,7 +6,7 @@ package akka.http.model.japi;
/**
* Represents a charset in Http. See {@link HttpCharsets} for a set of predefined charsets and
* static constructors to create and register custom charsets.
* static constructors to create custom charsets.
*/
public abstract class HttpCharset {
/**
@ -34,7 +34,7 @@ public abstract class HttpCharset {
public abstract HttpCharsetRange withQValue(float qValue);
/**
* Returns the predefined (and registered) alias names for this charset.
* Returns the predefined alias names for this charset.
*/
public abstract Iterable<String> getAliases();
}

View file

@ -21,13 +21,10 @@ public final class HttpCharsets {
public static final HttpCharset UTF_16LE = akka.http.model.HttpCharsets.UTF$minus16LE();
/**
* Registers a custom charset. Returns Some(newCharset) if the charset is supported by this JVM.
* Returns None otherwise.
* Create and return a custom charset.
*/
public static Option<HttpCharset> registerCustom(String value, String... aliases) {
scala.Option<akka.http.model.HttpCharset> custom = akka.http.model.HttpCharset.custom(value, Util.<String, String>convertArray(aliases));
if (custom.isDefined()) return Option.<HttpCharset>some(akka.http.model.HttpCharsets.register(custom.get()));
else return Option.none();
public static HttpCharset custom(String value, String... aliases) {
return akka.http.model.HttpCharset.custom(value, Util.<String, String>convertArray(aliases));
}
/**

View file

@ -6,7 +6,7 @@ package akka.http.model.japi;
/**
* Represents an HTTP request method. See {@link HttpMethods} for a set of predefined methods
* and static constructors to build and register custom ones.
* and static constructors to create custom ones.
*/
public abstract class HttpMethod {
/**

View file

@ -24,12 +24,15 @@ public final class HttpMethods {
public static final HttpMethod TRACE = akka.http.model.HttpMethods.TRACE();
/**
* Register a custom method type.
* Create a custom method type.
*/
public static HttpMethod registerCustom(String value, boolean safe, boolean idempotent, boolean entityAccepted) {
return akka.http.model.HttpMethods.register(akka.http.model.HttpMethod.custom(value, safe, idempotent, entityAccepted));
public static HttpMethod custom(String value, boolean safe, boolean idempotent, boolean entityAccepted) {
return akka.http.model.HttpMethod.custom(value, safe, idempotent, entityAccepted);
}
/**
* Looks up a predefined HTTP method with the given name.
*/
public static Option<HttpMethod> lookup(String name) {
return Util.<HttpMethod, akka.http.model.HttpMethod>lookupInRegistry(HttpMethods$.MODULE$, name);
}

View file

@ -179,16 +179,16 @@ public abstract class MediaTypes {
public static final MediaType VIDEO_WEBM = akka.http.model.MediaTypes.video$divwebm();
/**
* Register a custom media type.
* Creates a custom media type.
*/
public static MediaType registerCustom(
public static MediaType custom(
String mainType,
String subType,
boolean compressible,
boolean binary,
Iterable<String> fileExtensions,
Map<String, String> params) {
return akka.http.model.MediaTypes.register(akka.http.model.MediaType.custom(mainType, subType, compressible, binary, Util.<String, String>convertIterable(fileExtensions), Util.convertMapToScala(params), false));
return akka.http.model.MediaType.custom(mainType, subType, compressible, binary, Util.<String, String>convertIterable(fileExtensions), Util.convertMapToScala(params), false);
}
/**

View file

@ -85,17 +85,17 @@ public final class StatusCodes {
public static final StatusCode NETWORK_CONNECT_TIMEOUT = akka.http.model.StatusCodes.NetworkConnectTimeout();
/**
* Registers a custom status code.
* Create a custom status code.
*/
public static StatusCode registerCustom(int intValue, String reason, String defaultMessage, boolean isSuccess, boolean allowsEntity) {
return akka.http.model.StatusCodes.registerCustom(intValue, reason, defaultMessage, isSuccess, allowsEntity);
public static StatusCode custom(int intValue, String reason, String defaultMessage, boolean isSuccess, boolean allowsEntity) {
return akka.http.model.StatusCodes.custom(intValue, reason, defaultMessage, isSuccess, allowsEntity);
}
/**
* Registers a custom status code.
* Create a custom status code.
*/
public static StatusCode registerCustom(int intValue, String reason, String defaultMessage) {
return akka.http.model.StatusCodes.registerCustom(intValue, reason, defaultMessage);
public static StatusCode custom(int intValue, String reason, String defaultMessage) {
return akka.http.model.StatusCodes.custom(intValue, reason, defaultMessage);
}
/**

View file

@ -116,6 +116,7 @@ akka.http {
parsing {
# The limits for the various parts of the HTTP message parser.
max-uri-length = 2k
max-method-length = 16
max-response-reason-length = 64
max-header-name-length = 64
max-header-value-length = 8k

View file

@ -40,18 +40,20 @@ private[http] class HttpRequestParser(_settings: ParserSettings,
def parseMethod(input: ByteString, cursor: Int): Int = {
@tailrec def parseCustomMethod(ix: Int = 0, sb: JStringBuilder = new JStringBuilder(16)): Int =
if (ix < 16) { // hard-coded maximum custom method length
if (ix < maxMethodLength) {
byteChar(input, cursor + ix) match {
case ' '
HttpMethods.getForKey(sb.toString) match {
customMethods(sb.toString) match {
case Some(m)
method = m
cursor + ix + 1
case None parseCustomMethod(Int.MaxValue, sb)
case None throw new ParsingException(NotImplemented, ErrorInfo("Unsupported HTTP method", sb.toString))
}
case c parseCustomMethod(ix + 1, sb.append(c))
}
} else throw new ParsingException(NotImplemented, ErrorInfo("Unsupported HTTP method", sb.toString))
} else throw new ParsingException(BadRequest,
ErrorInfo("Unsupported HTTP method", s"HTTP method too long (started with '${sb.toString}'). " +
"Increase `akka.http.server.parsing.max-method-length` to support HTTP methods with more characters."))
@tailrec def parseMethod(meth: HttpMethod, ix: Int = 1): Int =
if (ix == meth.value.length)

View file

@ -57,7 +57,7 @@ private[http] class HttpResponseParser(_settings: ParserSettings,
case 200 StatusCodes.OK
case _ StatusCodes.getForKey(code) match {
case Some(x) x
case None badStatusCode
case None customStatusCodes(code) getOrElse badStatusCode
}
}
cursor + 4

View file

@ -6,11 +6,12 @@ package akka.http.engine.parsing
import com.typesafe.config.Config
import scala.collection.JavaConverters._
import akka.http.model.Uri
import akka.http.model.{ StatusCode, HttpMethod, Uri }
import akka.http.util._
final case class ParserSettings(
maxUriLength: Int,
maxMethodLength: Int,
maxResponseReasonLength: Int,
maxHeaderNameLength: Int,
maxHeaderValueLength: Int,
@ -20,9 +21,12 @@ final case class ParserSettings(
maxChunkSize: Int,
uriParsingMode: Uri.ParsingMode,
illegalHeaderWarnings: Boolean,
headerValueCacheLimits: Map[String, Int]) extends HttpHeaderParser.Settings {
headerValueCacheLimits: Map[String, Int],
customMethods: String Option[HttpMethod],
customStatusCodes: Int Option[StatusCode]) extends HttpHeaderParser.Settings {
require(maxUriLength > 0, "max-uri-length must be > 0")
require(maxMethodLength > 0, "max-method-length must be > 0")
require(maxResponseReasonLength > 0, "max-response-reason-length must be > 0")
require(maxHeaderNameLength > 0, "max-header-name-length must be > 0")
require(maxHeaderValueLength > 0, "max-header-value-length must be > 0")
@ -35,6 +39,15 @@ final case class ParserSettings(
def headerValueCacheLimit(headerName: String): Int =
headerValueCacheLimits.getOrElse(headerName, defaultHeaderValueCacheLimit)
def withCustomMethods(methods: HttpMethod*): ParserSettings = {
val map = methods.map(m m.name -> m).toMap
copy(customMethods = map.get)
}
def withCustomStatusCodes(codes: StatusCode*): ParserSettings = {
val map = codes.map(c c.intValue -> c).toMap
copy(customStatusCodes = map.get)
}
}
object ParserSettings extends SettingsCompanion[ParserSettings]("akka.http.parsing") {
@ -43,6 +56,7 @@ object ParserSettings extends SettingsCompanion[ParserSettings]("akka.http.parsi
apply(
c getIntBytes "max-uri-length",
c getIntBytes "max-method-length",
c getIntBytes "max-response-reason-length",
c getIntBytes "max-header-name-length",
c getIntBytes "max-header-value-length",
@ -52,7 +66,9 @@ object ParserSettings extends SettingsCompanion[ParserSettings]("akka.http.parsi
c getIntBytes "max-chunk-size",
Uri.ParsingMode(c getString "uri-parsing-mode"),
c getBoolean "illegal-header-warnings",
cacheConfig.entrySet.asScala.map(kvp kvp.getKey -> cacheConfig.getInt(kvp.getKey))(collection.breakOut))
cacheConfig.entrySet.asScala.map(kvp kvp.getKey -> cacheConfig.getInt(kvp.getKey))(collection.breakOut),
_ None,
_ None)
}
}

View file

@ -4,11 +4,12 @@
package akka.http.model
import java.lang.{ Iterable JIterable }
import language.implicitConversions
import scala.collection.immutable
import scala.util.Try
import java.nio.charset.Charset
import akka.http.util._
import java.lang.{ Iterable JIterable }
/**
* A charset range as encountered in `Accept-Charset`. Can either be a single charset, or `*`
@ -52,12 +53,14 @@ object HttpCharsetRange {
final case class HttpCharset private[http] (override val value: String)(val aliases: immutable.Seq[String])
extends japi.HttpCharset with SingletonValueRenderable with WithQValue[HttpCharsetRange] {
@transient private[this] var _nioCharset: Charset = Charset.forName(value)
def nioCharset: Charset = _nioCharset
@transient private[this] var _nioCharset: Try[Charset] = HttpCharset.findNioCharset(value)
/** Returns the Charset for this charset if available or throws an exception otherwise */
def nioCharset: Charset = _nioCharset.get
private def readObject(in: java.io.ObjectInputStream): Unit = {
in.defaultReadObject()
_nioCharset = Charset.forName(value)
_nioCharset = HttpCharset.findNioCharset(value)
}
def withQValue(qValue: Float): HttpCharsetRange = HttpCharsetRange(this, qValue.toFloat)
@ -70,17 +73,15 @@ final case class HttpCharset private[http] (override val value: String)(val alia
}
object HttpCharset {
def custom(value: String, aliases: String*): Option[HttpCharset] =
try Some(HttpCharset(value)(immutable.Seq(aliases: _*)))
catch {
// per documentation all exceptions thrown by `Charset.forName` are IllegalArgumentExceptions
case e: IllegalArgumentException None
}
def custom(value: String, aliases: String*): HttpCharset =
HttpCharset(value)(immutable.Seq(aliases: _*))
private[http] def findNioCharset(name: String): Try[Charset] = Try(Charset.forName(name))
}
// see http://www.iana.org/assignments/character-sets
object HttpCharsets extends ObjectRegistry[String, HttpCharset] {
def register(charset: HttpCharset): HttpCharset = {
private def register(charset: HttpCharset): HttpCharset = {
charset.aliases.foreach(alias register(alias.toRootLowerCase, charset))
register(charset.value.toRootLowerCase, charset)
}

View file

@ -4,58 +4,49 @@
package akka.http.model
import akka.http.util.{ SingletonValueRenderable, ObjectRegistry }
import scala.collection.immutable
import akka.http.util._
/**
* The method of an HTTP request.
* @param fingerprint unique Int value for faster equality checks (uniqueness is verified during registration)
* @param isSafe true if the resource should not be altered on the server
* @param isIdempotent true if requests can be safely (& automatically) repeated
* @param isEntityAccepted true if meaning of request entities is properly defined
*/
final case class HttpMethod private[http] (override val value: String,
fingerprint: Int,
isSafe: Boolean,
isIdempotent: Boolean,
isEntityAccepted: Boolean) extends japi.HttpMethod with SingletonValueRenderable {
def name = value
override def hashCode(): Int = fingerprint
override def equals(obj: Any): Boolean =
obj match {
case m: HttpMethod fingerprint == m.fingerprint
case _ false
}
override def toString: String = s"HttpMethod($value)"
}
object HttpMethod {
def custom(value: String, safe: Boolean, idempotent: Boolean, entityAccepted: Boolean): HttpMethod =
custom(value, value.##, safe, idempotent, entityAccepted)
def custom(value: String, fingerprint: Int, safe: Boolean, idempotent: Boolean, entityAccepted: Boolean): HttpMethod = {
require(value.nonEmpty, "value must be non-empty")
def custom(name: String, safe: Boolean, idempotent: Boolean, entityAccepted: Boolean): HttpMethod = {
require(name.nonEmpty, "value must be non-empty")
require(!safe || idempotent, "An HTTP method cannot be safe without being idempotent")
apply(value, fingerprint, safe, idempotent, entityAccepted)
apply(name, safe, idempotent, entityAccepted)
}
/**
* Creates a custom method by name and assumes properties conservatively to be
* safe = idempotent = false and entityAccepted = true.
*/
def custom(name: String): HttpMethod = custom(name, safe = false, idempotent = false, entityAccepted = true)
}
object HttpMethods extends ObjectRegistry[String, HttpMethod] {
def register(method: HttpMethod): HttpMethod = {
require(registry.values.forall(_.fingerprint != method.fingerprint), "Method fingerprint collision")
register(method.value, method)
}
private def register(method: HttpMethod): HttpMethod = register(method.value, method)
// format: OFF
val CONNECT = register(HttpMethod("CONNECT", 0x01, isSafe = false, isIdempotent = false, isEntityAccepted = false))
val DELETE = register(HttpMethod("DELETE" , 0x02, isSafe = false, isIdempotent = true , isEntityAccepted = false))
val GET = register(HttpMethod("GET" , 0x03, isSafe = true , isIdempotent = true , isEntityAccepted = false))
val HEAD = register(HttpMethod("HEAD" , 0x04, isSafe = true , isIdempotent = true , isEntityAccepted = false))
val OPTIONS = register(HttpMethod("OPTIONS", 0x05, isSafe = true , isIdempotent = true , isEntityAccepted = true))
val PATCH = register(HttpMethod("PATCH" , 0x06, isSafe = false, isIdempotent = false, isEntityAccepted = true))
val POST = register(HttpMethod("POST" , 0x07, isSafe = false, isIdempotent = false, isEntityAccepted = true))
val PUT = register(HttpMethod("PUT" , 0x08, isSafe = false, isIdempotent = true , isEntityAccepted = true))
val TRACE = register(HttpMethod("TRACE" , 0x09, isSafe = true , isIdempotent = true , isEntityAccepted = false))
val CONNECT = register(HttpMethod("CONNECT", isSafe = false, isIdempotent = false, isEntityAccepted = false))
val DELETE = register(HttpMethod("DELETE" , isSafe = false, isIdempotent = true , isEntityAccepted = false))
val GET = register(HttpMethod("GET" , isSafe = true , isIdempotent = true , isEntityAccepted = false))
val HEAD = register(HttpMethod("HEAD" , isSafe = true , isIdempotent = true , isEntityAccepted = false))
val OPTIONS = register(HttpMethod("OPTIONS", isSafe = true , isIdempotent = true , isEntityAccepted = true))
val PATCH = register(HttpMethod("PATCH" , isSafe = false, isIdempotent = false, isEntityAccepted = true))
val POST = register(HttpMethod("POST" , isSafe = false, isIdempotent = false, isEntityAccepted = true))
val PUT = register(HttpMethod("PUT" , isSafe = false, isIdempotent = true , isEntityAccepted = true))
val TRACE = register(HttpMethod("TRACE" , isSafe = true , isIdempotent = true , isEntityAccepted = false))
// format: ON
}

View file

@ -195,9 +195,7 @@ sealed abstract class NonMultipartMediaType private[http] (_value: String, _main
object MediaType {
/**
* Allows the definition of custom media types. In order for your custom type to be properly used by the
* HTTP layer you need to create an instance, register it via `MediaTypes.register` and use this instance in
* your custom Marshallers and Unmarshallers.
* Create a custom media type.
*/
def custom(mainType: String, subType: String, compressible: Boolean = false, binary: Boolean = false,
fileExtensions: immutable.Seq[String] = Nil, params: Map[String, String] = Map.empty,
@ -224,9 +222,9 @@ object MediaType {
}
object MediaTypes extends ObjectRegistry[(String, String), MediaType] {
@volatile private[this] var extensionMap = Map.empty[String, MediaType]
private[this] var extensionMap = Map.empty[String, MediaType]
def register(mediaType: MediaType): MediaType = synchronized {
private def register(mediaType: MediaType): MediaType = {
def registerFileExtension(ext: String): Unit = {
val lcExt = ext.toRootLowerCase
require(!extensionMap.contains(lcExt), s"Extension '$ext' clash: media-types '${extensionMap(lcExt)}' and '$mediaType'")

View file

@ -62,23 +62,22 @@ object StatusCodes extends ObjectRegistry[Int, StatusCode] {
}
/**
* Create and register a custom status code and allow full customization of behavior. The value of `allowsEntity`
* Create a custom status code and allow full customization of behavior. The value of `allowsEntity`
* changes the parser behavior: If it is set to true, a response with this status code is required to include a
* `Content-Length` header to be parsed correctly when keep-alive is enabled (which is the default in HTTP/1.1).
* If `allowsEntity` is false, an entity is never expected.
*/
def registerCustom(intValue: Int, reason: String, defaultMessage: String, isSuccess: Boolean, allowsEntity: Boolean): StatusCode =
reg(CustomStatusCode(intValue)(reason, defaultMessage, isSuccess, allowsEntity))
def custom(intValue: Int, reason: String, defaultMessage: String, isSuccess: Boolean, allowsEntity: Boolean): StatusCode =
StatusCodes.CustomStatusCode(intValue)(reason, defaultMessage, isSuccess, allowsEntity)
/** Create and register a custom status code with default behavior for its value region. */
def registerCustom(intValue: Int, reason: String, defaultMessage: String = ""): StatusCode = reg {
/** Create a custom status code with default behavior for its value region. */
def custom(intValue: Int, reason: String, defaultMessage: String = ""): StatusCode =
if (100 to 199 contains intValue) Informational(intValue)(reason, defaultMessage)
else if (200 to 299 contains intValue) Success(intValue)(reason, defaultMessage)
else if (300 to 399 contains intValue) Redirection(intValue)(reason, defaultMessage, defaultMessage)
else if (400 to 499 contains intValue) ClientError(intValue)(reason, defaultMessage)
else if (500 to 599 contains intValue) ServerError(intValue)(reason, defaultMessage)
else throw new IllegalArgumentException("Can't register status code in non-standard region")
}
import Informational.{apply => i}
import Success .{apply => s}

View file

@ -48,11 +48,6 @@ object HttpEncoding {
// see http://www.iana.org/assignments/http-parameters/http-parameters.xml
object HttpEncodings extends ObjectRegistry[String, HttpEncoding] {
def register(encoding: HttpEncoding): HttpEncoding =
register(encoding.value.toRootLowerCase, encoding)
private def register(value: String): HttpEncoding = register(HttpEncoding(value))
// format: OFF
val compress = register("compress")
val chunked = register("chunked")
@ -62,4 +57,7 @@ object HttpEncodings extends ObjectRegistry[String, HttpEncoding] {
val `x-compress` = register("x-compress")
val `x-zip` = register("x-zip")
// format: ON
private def register(encoding: HttpEncoding): HttpEncoding = register(encoding.value.toRootLowerCase, encoding)
private def register(value: String): HttpEncoding = register(HttpEncoding(value))
}

View file

@ -35,6 +35,5 @@ private[parser] trait CommonActions {
def getCharset(name: String): HttpCharset =
HttpCharsets
.getForKeyCaseInsensitive(name)
.orElse(HttpCharset.custom(name))
.getOrElse(throw new ParsingException("Unsupported charset", name))
.getOrElse(HttpCharset.custom(name))
}

View file

@ -408,7 +408,7 @@ private[parser] trait CommonRules { this: Parser with StringBuilding ⇒
token ~> { s
HttpMethods.getForKey(s) match {
case Some(m) m
case None throw new ParsingException("Unknown HTTP method", s)
case None HttpMethod.custom(s)
}
}
}

View file

@ -7,21 +7,18 @@ package akka.http.util
/**
* INTERNAL API
*
* A registry to keep track of singleton instances similar to what
* java.lang.Enum provides.
* A unsynchronized registry to keep track of singleton instances similar to what
* java.lang.Enum provides. `registry` should therefore only be used inside of singleton constructors.
*/
private[http] trait ObjectRegistry[K, V <: AnyRef] {
@volatile private[this] var _registry = Map.empty[K, V]
private[this] var _registry = Map.empty[K, V]
protected final def register(key: K, obj: V): obj.type = synchronized {
protected final def register(key: K, obj: V): obj.type = {
require(!_registry.contains(key), s"ObjectRegistry for ${getClass.getSimpleName} already contains value for $key")
_registry = _registry.updated(key, obj)
obj
}
protected def registry: Map[K, V] = _registry
def getForKey(key: K): Option[V] = registry.get(key)
def getForKey(key: K): Option[V] = _registry.get(key)
def getForKeyCaseInsensitive(key: String)(implicit conv: String <:< K): Option[V] =
getForKey(conv(key.toRootLowerCase))

View file

@ -35,7 +35,7 @@ class RequestParserSpec extends FreeSpec with Matchers with BeforeAndAfterAll {
implicit val system = ActorSystem(getClass.getSimpleName, testConf)
import system.dispatcher
val BOLT = HttpMethods.register(HttpMethod.custom("BOLT", safe = false, idempotent = true, entityAccepted = true))
val BOLT = HttpMethod.custom("BOLT", safe = false, idempotent = true, entityAccepted = true)
implicit val materializer = FlowMaterializer()
"The request parsing logic should" - {
@ -124,6 +124,9 @@ class RequestParserSpec extends FreeSpec with Matchers with BeforeAndAfterAll {
}
"with a custom HTTP method" in new Test {
override protected def parserSettings: ParserSettings =
super.parserSettings.withCustomMethods(BOLT)
"""BOLT / HTTP/1.0
|
|""" should parseTo(HttpRequest(BOLT, "/", protocol = `HTTP/1.0`))
@ -266,6 +269,14 @@ class RequestParserSpec extends FreeSpec with Matchers with BeforeAndAfterAll {
"GETX " should parseToError(NotImplemented, ErrorInfo("Unsupported HTTP method", "GETX"))
}
"a too long HTTP method" in new Test {
"ABCDEFGHIJKLMNOPQ " should
parseToError(BadRequest,
ErrorInfo(
"Unsupported HTTP method",
"HTTP method too long (started with 'ABCDEFGHIJKLMNOP'). Increase `akka.http.server.parsing.max-method-length` to support HTTP methods with more characters."))
}
"two Content-Length headers" in new Test {
"""GET / HTTP/1.1
|Content-Length: 3
@ -375,7 +386,8 @@ class RequestParserSpec extends FreeSpec with Matchers with BeforeAndAfterAll {
Await.result(future, 250.millis)
}
private def newParser = new HttpRequestParser(ParserSettings(system), false)()
protected def parserSettings: ParserSettings = ParserSettings(system)
protected def newParser = new HttpRequestParser(parserSettings, false)()
private def compactEntity(entity: RequestEntity): Deferrable[RequestEntity] =
entity match {

View file

@ -34,7 +34,7 @@ class ResponseParserSpec extends FreeSpec with Matchers with BeforeAndAfterAll {
import system.dispatcher
implicit val materializer = FlowMaterializer()
val ServerOnTheMove = StatusCodes.registerCustom(331, "Server on the move")
val ServerOnTheMove = StatusCodes.custom(331, "Server on the move")
"The response parsing logic should" - {
"properly parse" - {
@ -56,6 +56,9 @@ class ResponseParserSpec extends FreeSpec with Matchers with BeforeAndAfterAll {
}
"a response with a custom status code" in new Test {
override def parserSettings: ParserSettings =
super.parserSettings.withCustomStatusCodes(ServerOnTheMove)
"""HTTP/1.1 331 Server on the move
|Content-Length: 0
|
@ -233,8 +236,9 @@ class ResponseParserSpec extends FreeSpec with Matchers with BeforeAndAfterAll {
Await.result(future, 250.millis)
}
def parserSettings: ParserSettings = ParserSettings(system)
def newParser(requestMethod: HttpMethod = GET) = {
val parser = new HttpResponseParser(ParserSettings(system),
val parser = new HttpResponseParser(parserSettings,
dequeueRequestMethodForNextResponse = () requestMethod)()
parser
}

View file

@ -27,7 +27,7 @@ class ResponseRendererSpec extends FreeSpec with Matchers with BeforeAndAfterAll
implicit val system = ActorSystem(getClass.getSimpleName, testConf)
import system.dispatcher
val ServerOnTheMove = StatusCodes.registerCustom(330, "Server on the move")
val ServerOnTheMove = StatusCodes.custom(330, "Server on the move")
implicit val materializer = FlowMaterializer()
"The response preparation logic should properly render" - {

View file

@ -31,7 +31,7 @@ class SerializabilitySpec extends WordSpec with Matchers {
}
"with accept-charset" in {
HttpRequest().withHeaders(`Accept-Charset`(HttpCharsets.`UTF-16`)) should beSerializable
HttpRequest().withHeaders(`Accept-Charset`(HttpCharset.custom("utf8").get)) should beSerializable
HttpRequest().withHeaders(`Accept-Charset`(HttpCharset.custom("utf8"))) should beSerializable
}
"with accepted encodings" in {
HttpRequest().withHeaders(`Accept-Encoding`(HttpEncodings.chunked)) should beSerializable

View file

@ -17,7 +17,8 @@ import HttpEncodings._
import HttpMethods._
class HttpHeaderSpec extends FreeSpec with Matchers {
val `application/vnd.spray` = MediaTypes.register(MediaType.custom("application/vnd.spray"))
val `application/vnd.spray` = MediaType.custom("application/vnd.spray")
val PROPFIND = HttpMethod.custom("PROPFIND")
"The HTTP header model must correctly parse and render the headers" - {
@ -49,6 +50,7 @@ class HttpHeaderSpec extends FreeSpec with Matchers {
`Accept-Charset`(`ISO-8859-1`, `UTF-16` withQValue 0, HttpCharsetRange.`*` withQValue 0.8).renderedTo(
"ISO-8859-1, UTF-16;q=0.0, *;q=0.8")
`Accept-Charset`(`UTF-16` withQValue 0.234567).toString shouldEqual "Accept-Charset: UTF-16;q=0.235"
"Accept-Charset: UTF-16, unsupported42" =!= `Accept-Charset`(`UTF-16`, HttpCharset.custom("unsupported42"))
}
"Access-Control-Allow-Credentials" in {
@ -61,6 +63,7 @@ class HttpHeaderSpec extends FreeSpec with Matchers {
"Access-Control-Allow-Methods" in {
"Access-Control-Allow-Methods: GET, POST" =!= `Access-Control-Allow-Methods`(GET, POST)
"Access-Control-Allow-Methods: GET, PROPFIND, POST" =!= `Access-Control-Allow-Methods`(GET, PROPFIND, POST)
}
"Access-Control-Allow-Origin" in {
@ -85,6 +88,7 @@ class HttpHeaderSpec extends FreeSpec with Matchers {
"Access-Control-Request-Method" in {
"Access-Control-Request-Method: POST" =!= `Access-Control-Request-Method`(POST)
"Access-Control-Request-Method: PROPFIND" =!= `Access-Control-Request-Method`(PROPFIND)
}
"Accept-Ranges" in {
@ -113,6 +117,7 @@ class HttpHeaderSpec extends FreeSpec with Matchers {
"Allow" in {
"Allow: " =!= Allow()
"Allow: GET, PUT" =!= Allow(GET, PUT)
"Allow: GET, PROPFIND, PUT" =!= Allow(GET, PROPFIND, PUT)
}
"Authorization" in {
@ -177,7 +182,7 @@ class HttpHeaderSpec extends FreeSpec with Matchers {
"Content-Type: text/xml; version=3; charset=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`(ContentType(`text/plain`, HttpCharset.custom("fancy-pants")))
"Content-Type: multipart/mixed; boundary=ABC123" =!=
`Content-Type`(ContentType(`multipart/mixed` withBoundary "ABC123"))
"Content-Type: multipart/mixed; boundary=\"ABC/123\"" =!=