+htc #16030 Age and Expires headers
* Added models/parsers for Age and Expires headers * Updated tests using RawHeader to use typed headers
This commit is contained in:
parent
0f1feac42e
commit
fb968eb4be
11 changed files with 97 additions and 28 deletions
|
|
@ -0,0 +1,17 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.http.model.japi.headers;
|
||||
|
||||
/**
|
||||
* Model for the `Age` header.
|
||||
* Specification: http://tools.ietf.org/html/rfc7234#section-5.1
|
||||
*/
|
||||
public abstract class Age extends akka.http.model.HttpHeader {
|
||||
public abstract long deltaSeconds();
|
||||
|
||||
public static Age create(long deltaSeconds) {
|
||||
return new akka.http.model.headers.Age(deltaSeconds);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.http.model.japi.headers;
|
||||
|
||||
import akka.http.model.japi.DateTime;
|
||||
|
||||
/**
|
||||
* Model for the `Expires` header.
|
||||
* Specification: http://tools.ietf.org/html/rfc7234#section-5.3
|
||||
*/
|
||||
public abstract class Expires extends akka.http.model.HttpHeader {
|
||||
public abstract DateTime date();
|
||||
|
||||
public static Expires create(DateTime date) {
|
||||
return new akka.http.model.headers.Expires(((akka.http.util.DateTime) date));
|
||||
}
|
||||
}
|
||||
|
|
@ -277,6 +277,13 @@ final case class `Access-Control-Request-Method`(method: HttpMethod) extends jap
|
|||
protected def companion = `Access-Control-Request-Method`
|
||||
}
|
||||
|
||||
// http://tools.ietf.org/html/rfc7234#section-5.1
|
||||
object Age extends ModeledCompanion
|
||||
final case class Age(deltaSeconds: Long) extends japi.headers.Age with ModeledHeader {
|
||||
def renderValue[R <: Rendering](r: R): r.type = r ~~ deltaSeconds
|
||||
protected def companion = Age
|
||||
}
|
||||
|
||||
// http://tools.ietf.org/html/rfc7231#section-7.4.1
|
||||
object Allow extends ModeledCompanion {
|
||||
def apply(methods: HttpMethod*): Allow = apply(immutable.Seq(methods: _*))
|
||||
|
|
@ -393,6 +400,13 @@ final case class ETag(etag: EntityTag) extends japi.headers.ETag with ModeledHea
|
|||
protected def companion = ETag
|
||||
}
|
||||
|
||||
// http://tools.ietf.org/html/rfc7234#section-5.3
|
||||
object Expires extends ModeledCompanion
|
||||
final case class Expires(date: DateTime) extends japi.headers.Expires with ModeledHeader {
|
||||
def renderValue[R <: Rendering](r: R): r.type = date.renderRfc1123DateTimeString(r)
|
||||
protected def companion = Expires
|
||||
}
|
||||
|
||||
// http://tools.ietf.org/html/rfc7232#section-3.1
|
||||
object `If-Match` extends ModeledCompanion {
|
||||
val `*` = `If-Match`(EntityTagRange.`*`)
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ object HeaderParser {
|
|||
"access-control-request-headers",
|
||||
"access-control-request-method",
|
||||
"accept",
|
||||
"age",
|
||||
"allow",
|
||||
"authorization",
|
||||
"cache-control",
|
||||
|
|
@ -84,6 +85,7 @@ object HeaderParser {
|
|||
"date",
|
||||
"etag",
|
||||
"expect",
|
||||
"expires",
|
||||
"host",
|
||||
"if-match",
|
||||
"if-modified-since",
|
||||
|
|
|
|||
|
|
@ -61,6 +61,9 @@ private[parser] trait SimpleHeaders { this: Parser with CommonRules with CommonA
|
|||
httpMethodDef ~ EOI ~> (`Access-Control-Request-Method`(_))
|
||||
}
|
||||
|
||||
// http://tools.ietf.org/html/rfc7234#section-5.1
|
||||
def age = rule { `delta-seconds` ~ EOI ~> (Age(_)) }
|
||||
|
||||
// http://tools.ietf.org/html/rfc7231#section-7.4.1
|
||||
def allow = rule {
|
||||
zeroOrMore(httpMethodDef).separatedBy(listSep) ~ EOI ~> (Allow(_))
|
||||
|
|
@ -109,6 +112,9 @@ private[parser] trait SimpleHeaders { this: Parser with CommonRules with CommonA
|
|||
ignoreCase("100-continue") ~ OWS ~ push(Expect.`100-continue`)
|
||||
}
|
||||
|
||||
// http://tools.ietf.org/html/rfc7234#section-5.3
|
||||
def `expires` = rule { `HTTP-date` ~ EOI ~> (Expires(_)) }
|
||||
|
||||
// http://tools.ietf.org/html/rfc7230#section-5.4
|
||||
// We don't accept scoped IPv6 addresses as they should not appear in the Host header,
|
||||
// see also https://issues.apache.org/bugzilla/show_bug.cgi?id=35122 (WONTFIX in Apache 2 issue) and
|
||||
|
|
|
|||
|
|
@ -114,11 +114,11 @@ class ClientServerSpec extends WordSpec with Matchers with BeforeAndAfterAll {
|
|||
|
||||
val serverOutSub = serverOut.expectSubscription()
|
||||
serverOutSub.expectRequest()
|
||||
serverOutSub.sendNext(HttpResponse(206, List(RawHeader("Age", "42")), chunkedEntity))
|
||||
serverOutSub.sendNext(HttpResponse(206, List(Age(42)), chunkedEntity))
|
||||
|
||||
val clientInSub = clientIn.expectSubscription()
|
||||
clientInSub.request(1)
|
||||
val HttpResponse(StatusCodes.PartialContent, List(RawHeader("Age", "42"), Server(_), Date(_)),
|
||||
val HttpResponse(StatusCodes.PartialContent, List(Age(42), Server(_), Date(_)),
|
||||
Chunked(`chunkedContentType`, chunkStream2), HttpProtocols.`HTTP/1.1`) = clientIn.expectNext()
|
||||
Await.result(chunkStream2.grouped(1000).runWith(Sink.head), 100.millis) shouldEqual chunks
|
||||
|
||||
|
|
|
|||
|
|
@ -110,19 +110,20 @@ class HttpHeaderParserSpec extends WordSpec with Matchers with BeforeAndAfterAll
|
|||
check {
|
||||
""" ┌─\r-\n- EmptyHeader
|
||||
| | ┌─c-h-a-r-s-e-t-:- (accept-charset)
|
||||
| | | | ┌─e-n-c-o-d-i-n-g-:- (accept-encoding)
|
||||
| | | └─l-a-n-g-u-a-g-e-:- (accept-language)
|
||||
| | ┌─p-t---r-a-n-g-e-s-:- (accept-ranges)
|
||||
| | ┌─p-t---e-n-c-o-d-i-n-g-:- (accept-encoding)
|
||||
| | | | | ┌─l-a-n-g-u-a-g-e-:- (accept-language)
|
||||
| | | | └─r-a-n-g-e-s-:- (accept-ranges)
|
||||
| | | | ┌─\r-\n- Accept: */*
|
||||
| | | └─:-(accept)- -*-/-*-\r-\n- Accept: */*
|
||||
| | | ┌─c-r-e-d-e-n-t-i-a-l-s-:- (access-control-allow-credentials)
|
||||
| | | ┌─h-e-a-d-e-r-s-:- (access-control-allow-headers)
|
||||
| | | ┌─a-l-l-o-w---m-e-t-h-o-d-s-:- (access-control-allow-methods)
|
||||
| | | ┌─a-l-l-o-w---c-r-e-d-e-n-t-i-a-l-s-:- (access-control-allow-credentials)
|
||||
| | | | | | ┌─h-e-a-d-e-r-s-:- (access-control-allow-headers)
|
||||
| | | | | | ┌─m-e-t-h-o-d-s-:- (access-control-allow-methods)
|
||||
| | | | | └─o-r-i-g-i-n-:- (access-control-allow-origin)
|
||||
| | | | └─e-x-p-o-s-e---h-e-a-d-e-r-s-:- (access-control-expose-headers)
|
||||
| ┌─a-c-c-e-s-s---c-o-n-t-r-o-l---m-a-x---a-g-e-:- (access-control-max-age)
|
||||
| | | | ┌─h-e-a-d-e-r-s-:- (access-control-request-headers)
|
||||
| | | └─r-e-q-u-e-s-t---m-e-t-h-o-d-:- (access-control-request-method)
|
||||
| | | | | ┌─e-x-p-o-s-e---h-e-a-d-e-r-s-:- (access-control-expose-headers)
|
||||
| | | | └─m-a-x---a-g-e-:- (access-control-max-age)
|
||||
| ┌─a-c-c-e-s-s---c-o-n-t-r-o-l---r-e-q-u-e-s-t---h-e-a-d-e-r-s-:- (access-control-request-headers)
|
||||
| | | └─m-e-t-h-o-d-:- (access-control-request-method)
|
||||
| | | ┌─g-e-:- (age)
|
||||
| | └─l-l-o-w-:- (allow)
|
||||
| | └─u-t-h-o-r-i-z-a-t-i-o-n-:- (authorization)
|
||||
| | ┌─a-c-h-e---c-o-n-t-r-o-l-:-(cache-control)- -m-a-x---a-g-e-=-0-\r-\n- Cache-Control: max-age=0
|
||||
|
|
@ -138,7 +139,8 @@ class HttpHeaderParserSpec extends WordSpec with Matchers with BeforeAndAfterAll
|
|||
|-c-o-o-k-i-e-:- (cookie)
|
||||
| | ┌─d-a-t-e-:- (date)
|
||||
| | | ┌─t-a-g-:- (etag)
|
||||
| | ┌─e-x-p-e-c-t-:-(expect)- -1-0-0---c-o-n-t-i-n-u-e-\r-\n- Expect: 100-continue
|
||||
| | | | ┌─e-c-t-:-(expect)- -1-0-0---c-o-n-t-i-n-u-e-\r-\n- Expect: 100-continue
|
||||
| | ┌─e-x-p-i-r-e-s-:- (expires)
|
||||
| | | └─h-o-s-t-:- (host)
|
||||
| | | ┌─a-t-c-h-:- (if-match)
|
||||
| | ┌─i-f---m-o-d-i-f-i-e-d---s-i-n-c-e-:- (if-modified-since)
|
||||
|
|
@ -160,7 +162,7 @@ class HttpHeaderParserSpec extends WordSpec with Matchers with BeforeAndAfterAll
|
|||
| └─x---f-o-r-w-a-r-d-e-d---f-o-r-:- (x-forwarded-for)
|
||||
|""" -> parser.formatTrie
|
||||
}
|
||||
parser.formatSizes shouldEqual "592 nodes, 40 branchData rows, 55 values"
|
||||
parser.formatSizes shouldEqual "602 nodes, 42 branchData rows, 57 values"
|
||||
parser.contentHistogram shouldEqual
|
||||
Map("connection" -> 3, "Content-Length" -> 1, "accept" -> 2, "cache-control" -> 2, "expect" -> 1)
|
||||
}
|
||||
|
|
@ -232,8 +234,8 @@ class HttpHeaderParserSpec extends WordSpec with Matchers with BeforeAndAfterAll
|
|||
}
|
||||
randomHeaders.take(300).foldLeft(0) {
|
||||
case (acc, rawHeader) ⇒ acc + parseAndCache(rawHeader.toString + "\r\nx", rawHeader)
|
||||
} shouldEqual 100 // number of cache hits
|
||||
parser.formatSizes shouldEqual "3050 nodes, 114 branchData rows, 255 values"
|
||||
} shouldEqual 99 // number of cache hits
|
||||
parser.formatSizes shouldEqual "3040 nodes, 115 branchData rows, 255 values"
|
||||
}
|
||||
|
||||
"continue parsing modelled headers even if the overall cache capacity is reached" in new TestSetup() {
|
||||
|
|
@ -245,7 +247,7 @@ class HttpHeaderParserSpec extends WordSpec with Matchers with BeforeAndAfterAll
|
|||
randomHostHeaders.take(300).foldLeft(0) {
|
||||
case (acc, header) ⇒ acc + parseAndCache(header.toString + "\r\nx", header)
|
||||
} shouldEqual 12 // number of cache hits
|
||||
parser.formatSizes shouldEqual "756 nodes, 49 branchData rows, 67 values"
|
||||
parser.formatSizes shouldEqual "766 nodes, 51 branchData rows, 69 values"
|
||||
}
|
||||
|
||||
"continue parsing raw headers even if the header-specific cache capacity is reached" in new TestSetup() {
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ class RequestRendererSpec extends FreeSpec with Matchers with BeforeAndAfterAll
|
|||
"POST request, a few headers (incl. a custom Host header) and no body" in new TestSetup() {
|
||||
HttpRequest(POST, "/abc/xyz", List(
|
||||
RawHeader("X-Fancy", "naa"),
|
||||
RawHeader("Age", "0"),
|
||||
Age(0),
|
||||
Host("spray.io", 9999))) should renderTo {
|
||||
"""POST /abc/xyz HTTP/1.1
|
||||
|X-Fancy: naa
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ class ResponseRendererSpec extends FreeSpec with Matchers with BeforeAndAfterAll
|
|||
}
|
||||
|
||||
"status 304 and a few headers" in new TestSetup() {
|
||||
HttpResponse(304, List(RawHeader("X-Fancy", "of course"), RawHeader("Age", "0"))) should renderTo {
|
||||
HttpResponse(304, List(RawHeader("X-Fancy", "of course"), Age(0))) should renderTo {
|
||||
"""HTTP/1.1 304 Not Modified
|
||||
|X-Fancy: of course
|
||||
|Age: 0
|
||||
|
|
@ -80,7 +80,7 @@ class ResponseRendererSpec extends FreeSpec with Matchers with BeforeAndAfterAll
|
|||
ResponseRenderingContext(
|
||||
requestMethod = HttpMethods.HEAD,
|
||||
response = HttpResponse(
|
||||
headers = List(RawHeader("Age", "30"), Connection("Keep-Alive")),
|
||||
headers = List(Age(30), Connection("Keep-Alive")),
|
||||
entity = "Small f*ck up overhere!")) should renderTo(
|
||||
"""HTTP/1.1 200 OK
|
||||
|Age: 30
|
||||
|
|
@ -95,7 +95,7 @@ class ResponseRendererSpec extends FreeSpec with Matchers with BeforeAndAfterAll
|
|||
|
||||
"a response with a Strict body," - {
|
||||
"status 400 and a few headers" in new TestSetup() {
|
||||
HttpResponse(400, List(RawHeader("Age", "30"), Connection("Keep-Alive")), "Small f*ck up overhere!") should renderTo {
|
||||
HttpResponse(400, List(Age(30), Connection("Keep-Alive")), "Small f*ck up overhere!") should renderTo {
|
||||
"""HTTP/1.1 400 Bad Request
|
||||
|Age: 30
|
||||
|Server: akka-http/1.0.0
|
||||
|
|
@ -108,7 +108,7 @@ class ResponseRendererSpec extends FreeSpec with Matchers with BeforeAndAfterAll
|
|||
}
|
||||
|
||||
"status 400, a few headers and a body with an explicitly suppressed Content Type header" in new TestSetup() {
|
||||
HttpResponse(400, List(RawHeader("Age", "30"), Connection("Keep-Alive")),
|
||||
HttpResponse(400, List(Age(30), Connection("Keep-Alive")),
|
||||
HttpEntity(contentType = ContentTypes.NoContentType, "Small f*ck up overhere!")) should renderTo {
|
||||
"""HTTP/1.1 400 Bad Request
|
||||
|Age: 30
|
||||
|
|
@ -136,7 +136,7 @@ class ResponseRendererSpec extends FreeSpec with Matchers with BeforeAndAfterAll
|
|||
}
|
||||
"a response with a Default (streamed with explicit content-length body," - {
|
||||
"status 400 and a few headers" in new TestSetup() {
|
||||
HttpResponse(400, List(RawHeader("Age", "30"), Connection("Keep-Alive")),
|
||||
HttpResponse(400, List(Age(30), Connection("Keep-Alive")),
|
||||
entity = Default(contentType = ContentTypes.`text/plain(UTF-8)`, 23, source(ByteString("Small f*ck up overhere!")))) should renderTo {
|
||||
"""HTTP/1.1 400 Bad Request
|
||||
|Age: 30
|
||||
|
|
@ -193,7 +193,7 @@ class ResponseRendererSpec extends FreeSpec with Matchers with BeforeAndAfterAll
|
|||
"a chunked response" - {
|
||||
"with empty entity" in new TestSetup() {
|
||||
pending // Disabled until #15981 is fixed
|
||||
HttpResponse(200, List(RawHeader("Age", "30")),
|
||||
HttpResponse(200, List(Age(30)),
|
||||
Chunked(ContentTypes.NoContentType, source())) should renderTo {
|
||||
"""HTTP/1.1 200 OK
|
||||
|Age: 30
|
||||
|
|
@ -206,7 +206,7 @@ class ResponseRendererSpec extends FreeSpec with Matchers with BeforeAndAfterAll
|
|||
|
||||
"with empty entity but non-default Content-Type" in new TestSetup() {
|
||||
pending // Disabled until #15981 is fixed
|
||||
HttpResponse(200, List(RawHeader("Age", "30")),
|
||||
HttpResponse(200, List(Age(30)),
|
||||
Chunked(ContentTypes.`application/json`, source())) should renderTo {
|
||||
"""HTTP/1.1 200 OK
|
||||
|Age: 30
|
||||
|
|
@ -238,7 +238,7 @@ class ResponseRendererSpec extends FreeSpec with Matchers with BeforeAndAfterAll
|
|||
"with one chunk and an explicit LastChunk" in new TestSetup() {
|
||||
HttpResponse(entity = Chunked(ContentTypes.`text/plain(UTF-8)`,
|
||||
source(Chunk(ByteString("body123"), """key=value;another="tl;dr""""),
|
||||
LastChunk("foo=bar", List(RawHeader("Age", "30"), RawHeader("Cache-Control", "public")))))) should renderTo {
|
||||
LastChunk("foo=bar", List(Age(30), RawHeader("Cache-Control", "public")))))) should renderTo {
|
||||
"""HTTP/1.1 200 OK
|
||||
|Server: akka-http/1.0.0
|
||||
|Date: Thu, 25 Aug 2011 09:10:29 GMT
|
||||
|
|
@ -292,7 +292,7 @@ class ResponseRendererSpec extends FreeSpec with Matchers with BeforeAndAfterAll
|
|||
requestProtocol = HttpProtocols.`HTTP/1.0`,
|
||||
response = HttpResponse(entity = Chunked(ContentTypes.`text/plain(UTF-8)`,
|
||||
source(Chunk(ByteString("body123"), """key=value;another="tl;dr""""),
|
||||
LastChunk("foo=bar", List(RawHeader("Age", "30"), RawHeader("Cache-Control", "public"))))))) should renderTo(
|
||||
LastChunk("foo=bar", List(Age(30), RawHeader("Cache-Control", "public"))))))) should renderTo(
|
||||
"""HTTP/1.1 200 OK
|
||||
|Server: akka-http/1.0.0
|
||||
|Date: Thu, 25 Aug 2011 09:10:29 GMT
|
||||
|
|
|
|||
|
|
@ -114,6 +114,10 @@ class HttpHeaderSpec extends FreeSpec with Matchers {
|
|||
"Accept-Language: es-419, es" =!= `Accept-Language`(Language("es", "419"), Language("es"))
|
||||
}
|
||||
|
||||
"Age" in {
|
||||
"Age: 3600" =!= Age(3600)
|
||||
}
|
||||
|
||||
"Allow" in {
|
||||
"Allow: " =!= Allow()
|
||||
"Allow: GET, PUT" =!= Allow(GET, PUT)
|
||||
|
|
@ -237,6 +241,11 @@ class HttpHeaderSpec extends FreeSpec with Matchers {
|
|||
"Expect: 100-continue" =!= Expect.`100-continue`
|
||||
}
|
||||
|
||||
"Expires" in {
|
||||
"Expires: Wed, 13 Jul 2011 08:12:31 GMT" =!= Expires(DateTime(2011, 7, 13, 8, 12, 31))
|
||||
"Expires: 0" =!= Expires(DateTime.MinValue).renderedTo("Wed, 01 Jan 1800 00:00:00 GMT")
|
||||
}
|
||||
|
||||
"Host" in {
|
||||
"Host: www.spray.io:8080" =!= Host("www.spray.io", 8080)
|
||||
"Host: spray.io" =!= Host("spray.io")
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ class UnmarshallingSpec extends FreeSpec with Matchers with BeforeAndAfterAll wi
|
|||
|Content-type: text/xml
|
||||
|Age: 12
|
||||
|--XYZABC--""".stripMarginWithNewline("\r\n"))).to[Multipart.General] should haveParts(
|
||||
Multipart.General.BodyPart.Strict(HttpEntity.empty(MediaTypes.`text/xml`), List(RawHeader("Age", "12"))))
|
||||
Multipart.General.BodyPart.Strict(HttpEntity.empty(MediaTypes.`text/xml`), List(Age(12))))
|
||||
}
|
||||
"one non-empty part" in {
|
||||
Unmarshal(HttpEntity(`multipart/form-data` withBoundary "-",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue