Skip to content

Commit dec704a

Browse files
authored
Support appending header ranges to CorsSettings. (#940)
* Support appending header ranges to CorsSettings. Provide a CorsSettings builder method to allow additional header ranges to be added, and the underlying ability to concatenate HttpHeaderRange instances to support it. This supports scenarios where complex CorsSettings defaults are provided by downstream libraries (e.g. pekko-grpc) but still need to be extended/adapted by end user applications. * Add Javadoc and @SInCE 2.0.0 to new methods.
1 parent ecc078b commit dec704a

5 files changed

Lines changed: 158 additions & 1 deletion

File tree

http-cors/src/main/java/org/apache/pekko/http/cors/javadsl/model/HttpHeaderRange.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@
2424
public abstract class HttpHeaderRange {
2525
public abstract boolean matches(String header);
2626

27+
/**
28+
* Produces a new range that matches the headers of this range and the given range.
29+
*
30+
* @since 2.0.0
31+
*/
32+
public abstract HttpHeaderRange concat(HttpHeaderRange range);
33+
2734
public static HttpHeaderRange create(String... headers) {
2835
return HttpHeaderRange$.MODULE$.apply(Util.convertArray(headers));
2936
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The ASF licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
18+
# New methods added in 2.0.x
19+
ProblemFilters.exclude[ReversedMissingMethodProblem]("org.apache.pekko.http.cors.javadsl.model.HttpHeaderRange.concat")
20+
ProblemFilters.exclude[ReversedMissingMethodProblem]("org.apache.pekko.http.cors.scaladsl.model.HttpHeaderRange.concat")

http-cors/src/main/scala/org/apache/pekko/http/cors/CorsJavaMapping.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,8 @@ private[pekko] object CorsJavaMapping {
2727
object Implicits {
2828
implicit object CorsSettingsMapping
2929
extends JavaMapping.Inherited[javadsl.settings.CorsSettings, scaladsl.settings.CorsSettings]
30+
31+
implicit object HttpHeaderRangeMapping
32+
extends JavaMapping.Inherited[javadsl.model.HttpHeaderRange, scaladsl.model.HttpHeaderRange]
3033
}
3134
}

http-cors/src/main/scala/org/apache/pekko/http/cors/scaladsl/model/HttpHeaderRange.scala

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,39 @@ package org.apache.pekko.http.cors.scaladsl.model
2020
import scala.collection.immutable.Seq
2121

2222
import org.apache.pekko
23+
import pekko.http.cors.CorsJavaMapping.Implicits._
2324
import pekko.http.cors.javadsl
25+
import pekko.http.impl.util.JavaMapping
2426
import pekko.util.Helpers
2527

26-
abstract class HttpHeaderRange extends javadsl.model.HttpHeaderRange
28+
sealed abstract class HttpHeaderRange extends javadsl.model.HttpHeaderRange {
29+
override def concat(range: javadsl.model.HttpHeaderRange): HttpHeaderRange
30+
31+
/**
32+
* Operator alias for [[concat]].
33+
*
34+
* @since 2.0.0
35+
*/
36+
def ++(range: javadsl.model.HttpHeaderRange): HttpHeaderRange = concat(range)
37+
}
2738

2839
object HttpHeaderRange {
2940
case object `*` extends HttpHeaderRange {
3041
def matches(header: String) = true
42+
43+
override def concat(range: javadsl.model.HttpHeaderRange): HttpHeaderRange = this
3144
}
3245

3346
final case class Default(headers: Seq[String]) extends HttpHeaderRange {
3447
private val lowercaseHeaders: Seq[String] = headers.map(Helpers.toRootLowerCase)
3548
def matches(header: String): Boolean = lowercaseHeaders.contains(Helpers.toRootLowerCase(header))
49+
50+
override def concat(range: javadsl.model.HttpHeaderRange): HttpHeaderRange = {
51+
JavaMapping.toScala(range) match {
52+
case `*` => `*`
53+
case Default(headers) => Default(this.headers ++ headers)
54+
}
55+
}
3656
}
3757

3858
def apply(headers: String*): Default = Default(Seq(headers: _*))
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.pekko.http.cors.scaladsl.model
19+
20+
import org.apache.pekko.http.scaladsl.model.headers.{ `Content-Type`, Accept }
21+
import org.scalatest.Inspectors
22+
import org.scalatest.matchers.should.Matchers
23+
import org.scalatest.wordspec.AnyWordSpec
24+
25+
class HttpHeaderRangeSpec extends AnyWordSpec with Matchers with Inspectors {
26+
27+
"The `*` range" should {
28+
"match any Header" in {
29+
val headers = Seq(
30+
`Content-Type`.name,
31+
"conTent-tyPe",
32+
Accept.name,
33+
"x-any-random-header-name")
34+
35+
forAll(headers) { o => HttpHeaderRange.*.matches(o) shouldBe true }
36+
}
37+
38+
"be printed as `*`" in {
39+
HttpHeaderRange.*.toString shouldBe "*"
40+
}
41+
}
42+
43+
"The default range" should {
44+
val range = HttpHeaderRange(`Content-Type`.name, Accept.name)
45+
46+
"match headers ignoring case" in {
47+
val headers = Seq(
48+
`Content-Type`.name,
49+
`Content-Type`.name.toUpperCase(),
50+
`Content-Type`.name.toLowerCase(),
51+
"conTent-tyPe",
52+
Accept.name,
53+
Accept.name.toUpperCase(),
54+
Accept.name.toLowerCase(),
55+
"aCcepT")
56+
57+
forAll(headers) { o => range.matches(o) shouldBe true }
58+
}
59+
60+
"not match other headers" in {
61+
val headers = Seq(
62+
"Content-Type2",
63+
"Content-Typ",
64+
"x-any-random-header-name")
65+
66+
forAll(headers) { o => range.matches(o) shouldBe false }
67+
}
68+
}
69+
70+
"Concatenation of ranges" should {
71+
"match both ranges" in {
72+
val range1 = HttpHeaderRange(`Content-Type`.name)
73+
val range2 = HttpHeaderRange(Accept.name)
74+
75+
val combined = range1 ++ range2
76+
77+
val headers = Seq(
78+
`Content-Type`.name,
79+
Accept.name)
80+
val notHeaders = Seq(
81+
"Content-Type2",
82+
"Content-Typ",
83+
"x-any-random-header-name")
84+
85+
forAll(headers) { o => combined.matches(o) shouldBe true }
86+
forAll(notHeaders) { o => combined.matches(o) shouldBe false }
87+
}
88+
89+
"combine with the `*` range" in {
90+
val range1 = HttpHeaderRange(`Content-Type`.name)
91+
val starRange = HttpHeaderRange.*
92+
93+
val combinedBefore = starRange ++ range1
94+
val combinedAfter = range1 ++ starRange
95+
96+
val headers = Seq(
97+
`Content-Type`.name,
98+
"conTent-tyPe",
99+
Accept.name,
100+
"x-any-random-header-name")
101+
102+
forAll(headers) { o => combinedBefore.matches(o) shouldBe true }
103+
forAll(headers) { o => combinedAfter.matches(o) shouldBe true }
104+
}
105+
}
106+
107+
}

0 commit comments

Comments
 (0)