What you can do is copy the existing implementation in the JDK and modify that.
Note that this uses internal APIs and might only work on OpenJDK.
I initially wrote this on OpenJDK 16 so the copied implementation is a bit fancier than the one in OpenJDK 8, but there's no harm in that.
public class Main {
private static String PREFIX = "sun.net.www.protocol.";
public static void main(String[] args) throws IOException {
URL.setURLStreamHandlerFactory((protocol) -> {
// Avoid using reflection during bootstrap
switch (protocol) {
case "file":
return new sun.net.www.protocol.file.Handler();
case "jar":
return new sun.net.www.protocol.jar.Handler();
// Inject our own http and https handlers
case "http":
return new HttpUrlHandler();
case "https":
return new HttpsUrlHandler();
}
// The rest of this method is copied from the JDK 19 implementation
String name = PREFIX + protocol + ".Handler";
try {
Object o = Class.forName(name).getDeclaredConstructor().newInstance();
return (URLStreamHandler)o;
} catch (Exception e) {
// For compatibility, all Exceptions are ignored.
// any number of exceptions can get thrown here
}
return null;
});
byte[] buffer = new byte[1024];
new URL("http://stackoverflow.com").openStream().read(buffer);
System.out.println(new String(buffer, StandardCharsets.UTF_8));
new URL("https://stackoverflow.com").openStream().read(buffer);
System.out.println(new String(buffer, StandardCharsets.UTF_8));
}
private static class HttpUrlHandler extends sun.net.www.protocol.http.Handler {
@Override
protected URLConnection openConnection(URL u) throws IOException {
System.out.println("Overriding HTTP url");
return super.openConnection(new URL("http://httpbin.org/user-agent"));
}
}
private static class HttpsUrlHandler extends sun.net.www.protocol.https.Handler {
@Override
protected URLConnection openConnection(URL u) throws IOException {
System.out.println("Overriding HTTPS url");
return super.openConnection(new URL("https://httpbin.org/user-agent"));
}
}
}
What this does is extend the normal protocol handle by overriding openConnection
. The new implementation prints a message to standard out and then replace the URL with http(s)://httpbin.org/user-agent which responds with your user agent. The actual connection is then handled by the normal handler that we extend.
Note that if you do this on more recent JDK you may have to handle the jrt protocol in the switch
and you definitely will have to run and compile your application with a bunch of --add-exports
to enable access to the internal packages.
$ ./gradlew run
> Task :compileJava
/home/raniz/src/playground/src/main/java/playground/Main.java:20: warning: sun.net.www.protocol.file.Handler is internal proprietary API and may be removed in a future release
return new sun.net.www.protocol.file.Handler();
^
/home/raniz/src/playground/src/main/java/playground/Main.java:22: warning: sun.net.www.protocol.jar.Handler is internal proprietary API and may be removed in a future release
return new sun.net.www.protocol.jar.Handler();
^
/home/raniz/src/playground/src/main/java/playground/Main.java:47: warning: sun.net.www.protocol.http.Handler is internal proprietary API and may be removed in a future release
private static class HttpUrlHandler extends sun.net.www.protocol.http.Handler {
^
/home/raniz/src/playground/src/main/java/playground/Main.java:55: warning: sun.net.www.protocol.https.Handler is internal proprietary API and may be removed in a future release
private static class HttpsUrlHandler extends sun.net.www.protocol.https.Handler {
^
4 warnings
> Task :run
Overriding HTTP url
{
"user-agent": "Java/1.8.0_362"
}
Overriding HTTPS url
{
"user-agent": "Java/1.8.0_362"
}
BUILD SUCCESSFUL in 1s
2 actionable tasks: 2 executed
Another way to do this would be to use a HTTP client library that allows these kinds of extensions instead - Java's URL API is far from the most ergonomical.
I found these examples with a quick search:
Modify request URI before it is sent with apache http client
How to add headers to OkHttp request interceptor? (this one's for adding headers but the approach for changing the URL is the same)