DanielZ
06/11/2024, 7:40 PMclass ExampleShould {
@ParameterizedTest(name = "{index} - {1}")
@MethodSource("clients")
fun `clarify how to handle umlaute in header fields`(httpClient: HttpHandler, name: String) {
val customTrace = "mööp"
myService()
.asServer(SunHttp(0))
.start()
.use { main ->
val client = ClientFilters.SetBaseUriFrom(Uri.of("<http://localhost>:${main.port()}")).then(httpClient)
val response = shouldNotThrowAny {
client(
Request(GET, "/")
.with(Header.MY_CUSTOM_TRACING of customTrace)
)
}
response shouldHaveStatus I_M_A_TEAPOT
response.shouldHaveHeader(Header.MY_CUSTOM_TRACING, be(customTrace))
}
}
companion object {
@JvmStatic
fun clients(): List<Arguments> = listOf(
arguments(OkHttp(), "OkHttp"),
arguments(JavaHttpClient(), "JavaHttpClient"),
arguments(Java8HttpClient(), "Java8HttpClient"),
arguments(ApacheClient(), "ApacheClient"),
arguments(HelidonClient(), "HelidonClient"),
)
}
}
val Header.MY_CUSTOM_TRACING: BiDiLens<HttpMessage, String>
get() = Header.required("My-Custom-Tracing")
fun myService(): HttpHandler = { Response(I_M_A_TEAPOT).with(Header.MY_CUSTOM_TRACING of Header.MY_CUSTOM_TRACING(it)) }
Result is from returning passed in mööp
in custom tracing field to java.lang.AssertionError: Header "My-Custom-Tracing": expected:<"mööp"> but was:<"mööp">
and even throwing an exception.
Shouldn’t all clients behave similar? Which should be the desired one? (Spring keeps the original mööp
so I need either good arguments against such a value or at least a good strategy in http4k)
(That custom tracing field is used in structured logging.)
I’m aware that I can and should handle the exception thrown by OkHttp
in a Filter
. Is the only other option to switch to ApacheClient
?s4nchez
06/11/2024, 8:46 PMJames Richardson
06/11/2024, 9:10 PMJames Richardson
06/11/2024, 9:17 PMDanielZ
06/12/2024, 5:34 AMIllegalArgumentException
. In order to understand that challenge better I was writing the test above.
When I understand it correctly I could either disallow such header values or to encode it using base64 (which would create next challenges with other upstream services 😄 )
Most likely I would like to prevent such header values, sounds like it is the cleanest solution.James Richardson
06/12/2024, 6:07 PMDanielZ
06/12/2024, 6:11 PMJames Richardson
06/12/2024, 6:17 PMJames Richardson
06/12/2024, 6:28 PMclass TraceID(v: String) : StringValue(v) {
companion object : StringValueFactory<TraceID>(::TraceID, validation = "^trace-[a-z]+$".regex)
}
}
So that's defined a value type that can only be trace-xxxxxx
Now you can use a lens to get it, first define a lens
val traceID = Header.value(TraceID).required("my-trace-header")
Then in a handler, get the value:
val requestTraceID = traceID(request)
This way value you know will be strongly typed, and also validated!
This is written in slack on mobile, so might be a couple of typos!James Richardson
06/12/2024, 6:30 PMTraceID?
James Richardson
06/12/2024, 6:30 PM