diff --git a/akka-http-core/src/main/java/akka/http/model/japi/headers/CustomHeader.java b/akka-http-core/src/main/java/akka/http/model/japi/headers/CustomHeader.java new file mode 100644 index 0000000000..65eaf89fa8 --- /dev/null +++ b/akka-http-core/src/main/java/akka/http/model/japi/headers/CustomHeader.java @@ -0,0 +1,12 @@ +/* + * Copyright (C) 2009-2014 Typesafe Inc. + */ + +package akka.http.model.japi.headers; + +public abstract class CustomHeader extends akka.http.model.HttpHeader { + public abstract String name(); + public abstract String value(); + + protected abstract boolean suppressRendering(); +} diff --git a/akka-http-core/src/main/scala/akka/http/engine/rendering/HttpRequestRendererFactory.scala b/akka-http-core/src/main/scala/akka/http/engine/rendering/HttpRequestRendererFactory.scala index 759b3eab91..e4fea37385 100644 --- a/akka-http-core/src/main/scala/akka/http/engine/rendering/HttpRequestRendererFactory.scala +++ b/akka-http-core/src/main/scala/akka/http/engine/rendering/HttpRequestRendererFactory.scala @@ -77,6 +77,10 @@ private[http] class HttpRequestRendererFactory(userAgentHeader: Option[headers.` case x: `Raw-Request-URI` ⇒ // we never render this header renderHeaders(tail, hostHeaderSeen, userAgentSeen, transferEncodingSeen) + case x: CustomHeader ⇒ + if (!x.suppressRendering) render(x) + renderHeaders(tail, hostHeaderSeen, userAgentSeen, transferEncodingSeen) + case x: RawHeader if (x is "content-type") || (x is "content-length") || (x is "transfer-encoding") || (x is "host") || (x is "user-agent") ⇒ suppressionWarning(log, x, "illegal RawHeader") diff --git a/akka-http-core/src/main/scala/akka/http/engine/rendering/HttpResponseRendererFactory.scala b/akka-http-core/src/main/scala/akka/http/engine/rendering/HttpResponseRendererFactory.scala index 608e8d4073..98e20a4a12 100644 --- a/akka-http-core/src/main/scala/akka/http/engine/rendering/HttpResponseRendererFactory.scala +++ b/akka-http-core/src/main/scala/akka/http/engine/rendering/HttpResponseRendererFactory.scala @@ -105,6 +105,10 @@ private[http] class HttpResponseRendererFactory(serverHeader: Option[headers.Ser render(x) renderHeaders(tail, alwaysClose, connHeader, serverHeaderSeen = true, transferEncodingSeen) + case x: CustomHeader ⇒ + if (!x.suppressRendering) render(x) + renderHeaders(tail, alwaysClose, connHeader, serverHeaderSeen, transferEncodingSeen) + case x: RawHeader if (x is "content-type") || (x is "content-length") || (x is "transfer-encoding") || (x is "date") || (x is "server") || (x is "connection") ⇒ suppressionWarning(log, x, "illegal RawHeader") 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 7221523c2d..3ab41910a6 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 @@ -30,6 +30,17 @@ sealed trait ModeledHeader extends HttpHeader with Serializable { protected def companion: ModeledCompanion } +/** + * Superclass for user-defined custom headers defined by implementing `name` and `value`. + */ +abstract class CustomHeader extends japi.headers.CustomHeader { + /** Override to return true if this header shouldn't be rendered */ + def suppressRendering: Boolean = false + + def lowercaseName: String = name.toRootLowerCase + def render[R <: Rendering](r: R): r.type = r ~~ name ~~ ':' ~~ ' ' ~~ value +} + import japi.JavaMapping.Implicits._ // http://tools.ietf.org/html/rfc7230#section-6.1 diff --git a/akka-http-core/src/test/scala/akka/http/engine/rendering/RequestRendererSpec.scala b/akka-http-core/src/test/scala/akka/http/engine/rendering/RequestRendererSpec.scala index e4f0714ea2..96a54ba492 100644 --- a/akka-http-core/src/test/scala/akka/http/engine/rendering/RequestRendererSpec.scala +++ b/akka-http-core/src/test/scala/akka/http/engine/rendering/RequestRendererSpec.scala @@ -190,6 +190,35 @@ class RequestRendererSpec extends FreeSpec with Matchers with BeforeAndAfterAll } } } + "render a CustomHeader header" - { + "if suppressRendering = false" in new TestSetup(None) { + case class MyHeader(number: Int) extends CustomHeader { + def name: String = "X-My-Header" + def value: String = s"No$number" + } + HttpRequest(GET, "/abc", List(MyHeader(5))) should renderTo { + """GET /abc HTTP/1.1 + |X-My-Header: No5 + |Host: test.com:8080 + | + |""" + } + } + "not if suppressRendering = true" in new TestSetup(None) { + case class MyInternalHeader(number: Int) extends CustomHeader { + override def suppressRendering: Boolean = true + + def name: String = "X-My-Internal-Header" + def value: String = s"No$number" + } + HttpRequest(GET, "/abc", List(MyInternalHeader(5))) should renderTo { + """GET /abc HTTP/1.1 + |Host: test.com:8080 + | + |""" + } + } + } "properly use URI from Raw-Request-URI header if present" - { "GET request with Raw-Request-URI" in new TestSetup() { diff --git a/akka-http-core/src/test/scala/akka/http/engine/rendering/ResponseRendererSpec.scala b/akka-http-core/src/test/scala/akka/http/engine/rendering/ResponseRendererSpec.scala index 1a525e8c15..8be9406582 100644 --- a/akka-http-core/src/test/scala/akka/http/engine/rendering/ResponseRendererSpec.scala +++ b/akka-http-core/src/test/scala/akka/http/engine/rendering/ResponseRendererSpec.scala @@ -4,6 +4,7 @@ package akka.http.engine.rendering +import akka.http.model.HttpMethods._ import com.typesafe.config.{ Config, ConfigFactory } import scala.concurrent.duration._ import scala.concurrent.Await @@ -314,6 +315,38 @@ class ResponseRendererSpec extends FreeSpec with Matchers with BeforeAndAfterAll } } + "render a CustomHeader header" - { + "if suppressRendering = false" in new TestSetup(None) { + case class MyHeader(number: Int) extends CustomHeader { + def name: String = "X-My-Header" + def value: String = s"No$number" + } + HttpResponse(200, List(MyHeader(5))) should renderTo { + """HTTP/1.1 200 OK + |X-My-Header: No5 + |Date: Thu, 25 Aug 2011 09:10:29 GMT + |Content-Length: 0 + | + |""" + } + } + "not if suppressRendering = true" in new TestSetup(None) { + case class MyInternalHeader(number: Int) extends CustomHeader { + override def suppressRendering: Boolean = true + + def name: String = "X-My-Internal-Header" + def value: String = s"No$number" + } + HttpResponse(200, List(MyInternalHeader(5))) should renderTo { + """HTTP/1.1 200 OK + |Date: Thu, 25 Aug 2011 09:10:29 GMT + |Content-Length: 0 + | + |""" + } + } + } + "The 'Connection' header should be rendered correctly" in new TestSetup() { import org.scalatest.prop.TableDrivenPropertyChecks._ import HttpProtocols._