Question

I have a library that I need to use in one of my projects, which unfortunately registers its own URLStreamHandler to handle http-URLs. Is there a way to get a reference to Java's default http- and https-URLStreamHandlers, so I can specify one of them in the URL's constructor to open a standard http connection without using the protocol overridden by the library?

Was it helpful?

Solution

Found it:

sun.net.www.protocol.http.Handler

With that, I can now do:

URL url = new URL(null, "http://...", new sun.net.www.protocol.http.Handler());
HttpURLConnection cxn = (HttpURLConnection) url.openConnection();

And I get a normal Java HttpURLConnection and not the one provided by the library.


Update:

I discovered another, more general, approach: Remove the library's URLStreamHandlerFactory!

This is a bit tricky, since the URL-class technically doesn't allow you to set the factory more than once or to clear it with an official function call, but with a bit of reflection-magic, we can do it anyways:

public static String unsetURLStreamHandlerFactory() {
    try {
        Field f = URL.class.getDeclaredField("factory");
        f.setAccessible(true);
        Object curFac = f.get(null);
        f.set(null, null);
        URL.setURLStreamHandlerFactory(null);
        return curFac.getClass().getName();
    } catch (Exception e) {
        return null;
    }
}

This method grabs a hold of the static field factory in the URL-class, makes it accessible, grabs its current value and changes it to null. Afterwards, it calls URL.setStreamHandlerFactory(null) (which now completes without error) to make this setting "official", i.e. give the function a chance to do any other clean-up that it might want to do. Then it returns the previously registered factory's class name, just for reference. If anything goes wrong, it swallows the exception (I know, bad idea...) and returns null.

For reference: Here is the relevant source-code for URL.java.

Note: This approach might be even more risky than using the internal sun-classes (as far as portability goes) since it relies on the specific internal structure of the URL-class (namely the existence and exact function of the factory-field), but it does have the advantage that I don't need to go through all of my code to find all URL-constructors and add the handler-parameter... Also, it might break some functionality of the library that relies on their registered handlers. Luckily, neither issue (portability and partially broken library functionality) are issues that are relevant in my case.


Update: #2

While we're using reflection: Here is the probably safest way to get a reference to the default handler:

public static URLStreamHandler getURLStreamHandler(String protocol) {
    try {
        Method method = URL.class.getDeclaredMethod("getURLStreamHandler", String.class);
        method.setAccessible(true);
        return (URLStreamHandler) method.invoke(null, protocol);        
    } catch (Exception e) {
        return null;
    }
}

which you then simply call as:

URLStreamHandler hander = getURLStreamHandler("http");

Note: This call needs to happen before the library registers its URLStreamHandlerFactory, otherwise you will end up with a reference to their handler.

Why would I consider this the safest approach? Because URL.getURLStreamHandler(...) is not a fully private method, but only package-private. So, modifying its call-signature could break other code in the same package. Also, its name doesn't really leave much room for returning anything other than what we are looking for. Thus, I would expect it to be unlikely (albeit still not impossible) that a different/future implementation of the URL-class would be incompatible with the assumptions made here.

OTHER TIPS

Here's Markus A solution rewritten for Android:

public static URLStreamHandler getURLStreamHandler(String protocolIdentifier) {
    try {
        URL url = new URL(protocolIdentifier);
        Field handlerField = URL.class.getDeclaredField("handler");
        handlerField.setAccessible(true);
        return (URLStreamHandler)handlerField.get(url);
     } catch (Exception e) {
        return null;
    }
}

The method getURLStreamHandler doesn't exist in android, so it needs to be done differently. The protocolIdentifier is the protocol name plus the colon. You need to pass in a value so that can be used to instantiate a URL instance for the protocol URLStreamHandler you want. If you want the standard "jar:" protocol handler you must enter something like this: "jar:file:!/". The "file" could be replaced with "http" or "https", as they all get you the same handler instance.

The standard Handlers that android supports are http, https, file, jar, ftp.

You can register your own URLStreamHandlerFactory wirh URL.setURLStreamHandlerFactory(new URLStreamHandlerFactory());

public class URLStreamHandlerFactory implements java.net.URLStreamHandlerFactory {
    public URLStreamHandlerFactory() {}

    public URLStreamHandler createURLStreamHandler(String protocol) {
        if(protocol.equals("http")) {
            return new sun.net.www.protocol.http.Handler();
        } else if(protocol.equals("https")) {
            return new sun.net.www.protocol.https.Handler();
        }
        return null;
    }
}

so you can use standard handler

EDIT: Found this code

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top