Получение ссылки на Java по умолчанию http(s) URLStreamHandler

У меня есть библиотека, которую мне нужно использовать в одном из моих проектов, который, к сожалению, регистрирует свою собственную URLStreamHandler обрабатывать http-URLs, Есть ли способ получить ссылку на Java по умолчанию http- и https-URLStreamHandlersтак что я могу указать один из них в URLконструктор для открытия стандартного http-соединения без использования протокола, переопределенного библиотекой?

2 ответа

Решение

Нашел это:

sun.net.www.protocol.http.Handler

Теперь я могу сделать следующее:

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

И я получаю нормальный Java HttpURLConnection, а не тот, который предоставляется библиотекой.


Обновить:

Я обнаружил другой, более общий подход: удалить библиотеку URLStreamHandlerFactory!

Это немного сложно, так как технически URL-класс не позволяет вам устанавливать фабрику более одного раза или очищать ее с помощью официального вызова функции, но с небольшим количеством волшебства отражения, мы можем сделать это в любом случае:

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;
    }
}

Этот метод захватывает статическое поле factory в URL-class, делает его доступным, захватывает его текущее значение и изменяет его на null. После этого он вызывает URL.setStreamHandlerFactory (null) (который теперь завершается без ошибок), чтобы сделать этот параметр "официальным", т. Е. Дать функции возможность выполнить любую другую очистку, которая может потребоваться. Затем он возвращает имя класса ранее зарегистрированной фабрики, просто для справки. Если что-то пойдет не так, оно проглотит исключение (я знаю, плохая идея...) и вернет ноль.

Для справки: Вот соответствующий исходный код для URL.java.

Примечание. Этот подход может быть даже более рискованным, чем использование внутренних классов солнца (в том, что касается переносимости), поскольку он опирается на конкретную внутреннюю структуру класса URL (а именно на существование и точную функцию factory -field), но у него есть то преимущество, что мне не нужно просматривать весь мой код, чтобы найти все URL-конструкторы и добавить параметр-обработчик... Кроме того, это может нарушить некоторые функции библиотеки, которая полагается на их зарегистрированных обработчиков. К счастью, ни одна проблема (переносимость и частично нарушенная функциональность библиотеки) не являются проблемами, которые актуальны в моем случае.


Обновление: № 2

Пока мы используем рефлексию: вот, пожалуй, самый безопасный способ получить ссылку на обработчик по умолчанию:

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;
    }
}

который вы тогда просто называете как:

URLStreamHandler hander = getURLStreamHandler("http");

Примечание. Этот вызов должен произойти до того, как библиотека зарегистрирует URLStreamHandlerFactory в противном случае вы получите ссылку на их обработчик.

Почему я считаю это самым безопасным подходом? Так как URL.getURLStreamHandler(...) это не полностью приватный метод, а только пакетный. Таким образом, изменение его call-подписи может нарушить другой код в том же пакете. Кроме того, его имя на самом деле не оставляет много места для возврата чего-либо, кроме того, что мы ищем. Таким образом, я ожидаю, что маловероятно (хотя и не невозможно), что другая / будущая реализация URL -класс был бы несовместим с сделанными здесь предположениями.

Вот Маркус Решение, переписанное для 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;
    }
}

Метод getURLStreamHandler не существует в Android, поэтому его нужно делать по-другому. ProtocolIdentifier - это имя протокола плюс двоеточие. Вам нужно передать значение, чтобы его можно было использовать для создания экземпляра URL-адреса для требуемого протокола URLStreamHandler. Если вам нужен стандартный обработчик протокола "jar:", вы должны ввести что-то вроде этого: "jar:file:!/". "Файл" может быть заменен на "http" или "https", поскольку все они получают один и тот же экземпляр обработчика.

Стандартные обработчики, которые поддерживает Android, - это http, https, file, jar, ftp.

Вы можете зарегистрировать свой собственный URLStreamHandlerFactory спинным 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;
    }
}

так что вы можете использовать стандартный обработчик

РЕДАКТИРОВАТЬ: нашел этот код

Другие вопросы по тегам