TL;DR Java 9 HttpClient does not allow custom Authorization header unless you resort to a hack
Over the weekend, we decided to add experimental support for Java 9 to our in-house apns-http2 library. The goal was to take away the OkHttp dependency + requirement for Jetty’s ALPN library (not that this combination is unstable).
To my surprise, it’s impossible to set the “authorization” header! Well, it does support HTTP basic authentication, using a new callback method (which is quite a waste, more on that some other day). To authenticate with Apple’s push notification gateway (APNs endpoints) via HTTP/2, Apple’s documentation states that the “authorization” header be set to:
Setting it using HttpRequest.Builder doesn’t work:
final HttpRequest.Builder rb = HttpRequest .newBuilder() .header("authorization", "bearer " + cachedJWTToken);
On further investigation, it’s a forbidden header (after debugging the code right down). Internally, when headers are added via
jdk.incubator.http.HttpRequest.Builder#header, they go to a map named userHeaders. When the request is built, authorization is stripped off, as it’s a forbidden user header.
Fortunately, in the HttpRequestImpl class there’s a way to set system headers, which is where we ended up setting it. This is not a part of the public API, so we had to resort to reflection:
Class reqClass = request.getClass(); Method setHeaderMethod = reqClass.getDeclaredMethod("setSystemHeader", String.class, String.class); setHeaderMethod.setAccessible(true); setHeaderMethod.invoke(request, "authorization", "bearer " + cachedJWTToken);
Now, with Java 9 and it’s new module system, we had to explicitly add the following VM option:
The above opens up the package jdk.incubator.http for reflection (otherwise setAccessible fails).
And voilà! We could talk to APNs!
We’ve already filed a bug for this with Oracle, as setting the authorization header as a user header must be allowed. It’s been acknowledged and can be found here. A little more information about this can also be found here.