Spec-Zone .ru
спецификации, руководства, описания, API
Содержание документации

Сети Java и Прокси

1) Введение

В сегодняшних сетевых средах, особенно корпоративных, разработчики приложений должны иметь дело с прокси почти так часто как системные администраторы. В некоторых случаях приложение должно использовать настройки системного значения по умолчанию в других случаях, оно будет мы хотеть иметь очень жесткий контроль над тем, что проходит через, какой прокси, и, где-нибудь в середине, большинство приложений будет счастливо делегировать решение своим пользователям тем, если их с GUI, чтобы установить настройки прокси, как имеет место в большинстве браузеров.

В любом случае платформа разработки, как Java, должна обеспечить механизмы, чтобы иметь дело с этими прокси, которые и мощны и гибки. К сожалению, до недавнего времени, платформа Java не была очень гибка в том отделе. Но все, что изменилось в J2SE 5.0 как новый API, было представлено, чтобы адресовать этот недостаток, и цель этой бумаги состоит в том, чтобы обеспечить всестороннее объяснение всех этих API и механизмов, старых, которые все еще допустимы, так же как новые.

2) Системные Свойства

Вплоть до J2SE 1.4 системных свойства были единственным способом установить прокси-серверы в пределах Java сетевой API для любого из обработчиков протокола. Чтобы сделать вопросы более сложными, имена этих свойств изменились от одного выпуска до другого, и некоторые из них являются теперь устаревшими, даже если они все еще поддерживаются для пользы совместимости.

Главное ограничение использования системных свойств - то, что они "все или ничего" переключатель. Означая, что, как только прокси был установлен для определенного протокола, он будет влиять на все соединения для того протокола. Это - широкое поведение VM.

Есть 2 основных способа установить системные свойства:

Теперь, давайте смотреть, протокол протоколом, в свойствах, которые можно использовать, чтобы установить прокси. Все прокси определяются именем хоста и номером порта. Позже является дополнительным как, если это не будет определено, то стандартный порт по умолчанию будет использоваться.

2.1) HTTP

Есть 3 свойства, которые можно установить, чтобы определить прокси, который будет использоваться http обработчиком протокола:

Давайте смотреть на несколько примеров, предполагающих, что мы пытаемся выполнить основной метод класса GetURL:

$ java -Dhttp.proxyHost=webcache.example.com GetURL

Все http соединения пройдут через прокси-сервер в webcache.example.com слушание на порту 80 (мы не определяли порта, таким образом, значение по умолчанию каждый используется).

$ java -Dhttp.proxyHost=webcache.example.com -Dhttp.proxyPort=8080
-Dhttp.noProxyHosts=”localhost|host.example.com” GetURL

В том втором примере прокси-сервер все еще будет в webcache.example.com, но на сей раз слушая на порту 8080. Кроме того, прокси не будет использоваться, соединяясь с также localhost или host.mydonain.com.

Как отмечалось ранее эти настройки влияют на все http соединения во время всего времени жизни VM, вызванного с этими опциями. Однако это возможно, используя System.setProperty () метод, чтобы иметь немного более динамическое поведение.

Вот выборка кода, показывающая, как это может быть сделано:

//Set the http proxy to webcache.example.com:8080

System.setProperty("http.proxyHost", "webcache.example.com");
System.setProperty("http.proxyPort", "8080");

// Next connection will be through proxy.
URL url = new URL("http://java.example.org/");
InputStream in = url.openStream();

// Now, let's 'unset' the proxy.
System.setProperty("http.proxyHost", null);

// From now on http connections will be done directly.

Теперь, это работает разумно хорошо, даже если немного громоздкое, но это может стать хитрым, если Ваше приложение многопоточно. Помните, системные свойства являются настройками “VM wide”, таким образом, на все потоки влияют. Что означает, что код в одном потоке, как побочный эффект, мог представить код в другом недействующем потоке.

2.2) HTTPS

У https (http по SSL) обработчик протокола есть свой собственный набор свойств:

Поскольку Вы, вероятно, предположили, что они работают тем же самым способом их http дубликатами, таким образом, мы не будем проникать в большое количество деталей кроме упомянуть, что номер порта по умолчанию, на сей раз, 443 и что для "не проксируют узлы" список, обработчик протокола HTTPS будет использовать то же самое в качестве http обработчика (то есть. http.nonProxyHosts).

2.3) FTP

Настройки для обработчика протокола FTP следуют за теми же самыми правилами что касается http, единственная разница - то, что каждое имя свойства теперь снабжается префиксом'ftp.' вместо'http.'

Поэтому системные свойства:

Отметьте, что на сей раз есть отдельное свойство для "не, проксируют узлы" список. Кроме того, что касается http, значение номера порта по умолчанию 80. Нужно отметить, что, проходя через прокси, обработчик протокола FTP будет фактически использовать HTTP, чтобы дать команды к прокси-серверу, который объясняет, почему это - тот же самый номер порта по умолчанию.

Давайте исследуем быстрый пример:

$ java -Dhttp.proxyHost=webcache.example.com
-Dhttp.proxyPort=8080 -Dftp.proxyHost=webcache.example.com -Dftp.proxyPort=8080 GetURL

Здесь, и HTTP и обработчики протокола FTP будут использовать тот же самый прокси-сервер в webcache.example.com:8080.

2.4) SOCKS

Протокол SOCKS, как определено в RFC 1928, служит основой для клиент-серверных приложений, чтобы безопасно пересечь брандмауэр и в TCP и в уровне UDP. В этом смысле это - намного больше обобщения чем высокоуровневые прокси (как HTTP или FTP определенные прокси). J2SE 5.0 оказывает поддержку SOCKS для клиентских сокетов TCP.

Есть 2 системных свойства, связанные с SOCKS:

Отметьте, что нет никакой точки ('. ') после префикса на сей раз. Это по историческим причинам и гарантировать обратную совместимость. Как только прокси SOCKS определяется этим способом, все соединения TCP будут предприняты через прокси.

Пример:

$ java -DsocksProxyHost=socks.example.com GetURL

Здесь, во время выполнения кода, каждый исходящий сокет TCP пройдет через прокси-сервер SOCKS в socks.example.com:1080.

Теперь, что происходит, когда и прокси SOCKS и прокси HTTP определяются? Хорошо правило состоит в том, что настройки для высокоуровневых протоколов, как HTTP или FTP, имеют приоритет по настройкам SOCKS. Так, в том особом случае, устанавливая HTTP-соединение, будут проигнорированы настройки прокси SOCKS, и с прокси HTTP свяжутся. Давайте смотреть на пример:

$ java -Dhttp.proxyHost=webcache.example.com -Dhttp.proxyPort=8080
-DsocksProxyHost=socks.example.com GetURL

Здесь, http URL пройдет через webcache.example.com:8080 потому что http настройки имеют приоритет. Но что относительно протокола передачи файлов URL? Так как никакие определенные настройки прокси не были присвоены для FTP, и так как FTP сверху TCP, тогда соединения FTP будут предприняты через прокси-сервер SOCKS в socks.mydomsain.com:1080. Если бы прокси FTP был определен, то тот прокси использовался бы вместо этого.

3) Прокси-класс

Как мы видели, системные свойства мощны, но не гибки. "Все или ничего" поведение справедливо считало слишком серьезным ограничением большинство разработчиков. Именно поэтому было решено представить новый, более гибкий, API в J2SE 5.0 так, чтобы было возможно иметь соединение базируемые настройки прокси.

Ядром этого нового API является Прокси-класс, который представляет определение прокси, обычно тип (http, носки) и адрес сокета. Есть, с J2SE 5.0, 3 возможных типов:

Так, чтобы создать объект прокси HTTP, который что Вы вызвали бы:

SocketAddress addr = new
InetSocketAddress("webcache.example.com", 8080);
Proxy proxy = new Proxy(Proxy.Type.HTTP, addr);

Помните, этот новый объект прокси представляет определение прокси, ничто больше. Как мы используем такой объект? Новое openConnection() метод был добавлен к классу URL и берет Прокси в качестве параметра, это работает тот же самый путь как openConnection() без параметров, кроме этого вынуждает соединение быть установленным через указанный прокси, игнорируя все другие настройки, включая системные упомянутые выше свойства.

Так завершая предыдущий пример, мы можем теперь добавить:

URL url = new URL("http://java.example.org/");
URConnection conn = url.openConnection(proxy);

Простой, не так ли?

Тот же самый механизм может использоваться, чтобы определить, что определенный URL должен быть достигнут непосредственно, потому что это находится на интранет например. Это - то, где ПРЯМОЙ тип играет роль. Но, Вы не должны создать экземпляр прокси с ПРЯМЫМ типом, все, что необходимо сделать, использовать статический элемент NO_PROXY:

URL url2 = new URL("http://infos.example.com/");
URLConnection conn2 = url2.openConnection(Proxy.NO_PROXY);

Теперь, это гарантирует Вам, что этот определенный URL будет получен, хотя прямая связь, обходящая любые другие настройки прокси, которые могут быть удобными.

Отметьте, что можно вынудить URLConnection пройти через прокси SOCKS также:

SocketAddress addr = new InetSocketAddress("socks.example.com", 1080);
Proxy proxy = new Proxy(Proxy.Type.SOCKS, addr);
URL url = new URL("ftp://ftp.gnu.org/README");
URLConnection conn = url.openConnection(proxy);

То определенное соединение FTP будет предпринято хотя указанный прокси SOCKS. Как можно видеть, это довольно прямо.

Наконец, но не в последнюю очередь, можно также определить прокси для отдельных сокетов TCP при использовании недавно представленного конструктора сокета:

SocketAddress addr = new InetSocketAddress("socks.example.com", 1080);
Proxy proxy = new Proxy(Proxy.Type.SOCKS, addr);
Socket socket = new Socket(proxy);
InetSocketAddress dest = new InetSocketAddress("server.example.org", 1234);
socket.connect(dest);

Здесь сокет попытается соединиться с его адресом получателя (сервер example.org:1234) через указанный прокси SOCKS.

Что касается URL, тот же самый механизм может использоваться, чтобы гарантировать, что прямое (то есть не через любой прокси) соединение должно быть предпринято независимо от того, каковы глобальные настройки:

Socket socket = new Socket(Proxy.NO_PROXY);
socket.connect(new InetAddress("localhost", 1234));

Отметьте, что этот новый конструктор, с J2SE 5.0, принимает только 2 типа прокси: SOCKS или ПРЯМОЙ (то есть экземпляр NO_PROXY).

4) ProxySelector

Как можно видеть с J2SE 5.0, разработчик получает довольно мало контроля и гибкости когда дело доходит до прокси. Однако, есть ситуации, где можно было бы хотеть решить, какой прокси использовать динамически, например сделать некоторое выравнивание нагрузки между прокси, или в зависимости от места назначения, когда API, описываемый до сих пор, будет довольно громоздким. Это - то, где ProxySelector играет роль.

Вкратце ProxySelector является частью кода, который скажет обработчики протокола, которые проксируют, чтобы использовать, если таковые вообще имеются, для любого данного URL. Например, рассмотрите следующий код:

URL url = new URL("http://java.example.org/index.html");
URLConnection conn = url.openConnection();
InputStream in = conn.getInputStream();

В той точке вызывается обработчик протокола HTTP, и это запросит proxySelector. Диалоговое окно могло бы пойти что-то как этот:

Обработчик: Эй пижон, я пытаюсь достигнуть java.example.org, я должен использовать прокси?
ProxySelector: Какой протокол Вы намереваетесь использовать?
Обработчик: http, конечно!
ProxySelector: На порту по умолчанию?
Обработчик: Позвольте мне проверять.... Да, порт по умолчанию.
ProxySelector: Ясно. Затем Вы должны использовать webcache.example.com на порту 8080 как прокси.
Обработчик: Спасибо. <пауза> Пижон, webcache.example.com:8080, кажется, не отвечает! Какая-либо другая опция?
ProxySelector: Dang! OK, попробуйте webcache2.example.com на порту 8080 также.
Обработчик: Уверенный., Кажется, работает. Спасибо.
ProxySelector: Никакой пот. До свидания.

Конечно, я украшаю немного, но Вы получаете идею.

Лучшая вещь о ProxySelector состоит в том, что это - plugable! Что означает, что, если у Вас есть потребности, которые не удовлетворяются значением по умолчанию один, можно записать замену для него и включить его!

Так, каков ProxySelector? Давайте смотреть на определение класса:

public abstract class ProxySelector {
        public static ProxySelector getDefault();
        public static void setDefault(ProxySelector ps);
        public abstract List<Proxy> select(URI uri);
        public abstract void connectFailed(URI uri,
                SocketAddress sa, IOException ioe);
}

Как мы можем видеть, ProxySelector является абстрактным классом с 2 статическими методами, чтобы установить, или добраться, реализация по умолчанию, и 2 метода экземпляра, которые будут использоваться обработчиками протокола, чтобы определить, какой прокси использовать или уведомить, что прокси, кажется, недостижим. Если Вы хотите предоставить своему собственному ProxySelector, все, что необходимо сделать, расширяют этот класс, обеспечивают, реализация для этих 2 методов экземпляра тогда вызывают ProxySelector.setDefault () передача экземпляра Вашего нового класса как параметр. В этой точке обработчики протокола, как http или протокол передачи файлов, запросят новый ProxySelector, пытаясь решить что прокси использовать.

Прежде, чем мы будем видеть в деталях, как записать такой ProxySelector, давайте говорить о значении по умолчанию один. J2SE 5.0 обеспечивает реализацию по умолчанию, которая осуществляет обратную совместимость. В других сроках ProxySelector по умолчанию проверит системные свойства, описанные ранее, чтобы определить который прокси использовать. Однако, есть новая, дополнительная функция: На недавних системах Windows и на Gnome 2.x платформы возможно сказать, ProxySelector по умолчанию, чтобы использовать системные настройки прокси (и недавние версии Windows и Gnome 2.x позволяют Вам устанавливать прокси глобально через их пользовательский интерфейс). Если системное свойство java.net.useSystemProxies устанавливается в истину (по умолчанию, она устанавливается в ложь для пользы совместимости), тогда ProxySelector по умолчанию попытается использовать эти настройки. Можно установить то системное свойство на командной строке, или можно отредактировать файл установки JRE lib/net.properties, тем путем необходимо изменить это только однажды на данной системе.

Теперь давайте исследуем, как записать, и установить, новый ProxySelector.

Вот то, чего мы хотим достигнуть: мы довольно довольны поведением ProxySelector по умолчанию, кроме тех случаев, когда оно прибывает в http и https. В нашей сети у нас есть больше чем один возможный прокси для этих протоколов, и мы хотели бы, чтобы наше приложение судило их в последовательности (то есть: если 1-ый не отвечает, то попробуйте второй и так далее). Даже больше, если один из них приведет слишком многих к сбою время, то мы удалим это из списка, чтобы оптимизировать вещи немного.

Все, что мы должны сделать, разделить на подклассы java.net.ProxySelector и предоставьте реализации обоим select() и connectFailed() методы.

select() метод вызывают обработчики протокола прежде, чем попытаться соединиться с местом назначения. Параметром, который передают, является URI, описывающий ресурс (протокол, узел и номер порта). Метод тогда возвратит Список Прокси. Например следующий код:

URL url = new URL("http://java.example.org/index.html");
InputStream in = url.openStream();

инициирует следующий псевдовызов в обработчике протокола:

List<Proxy> l = ProxySelector.getDefault().select(new URI("http://java.example.org/"));

В нашей реализации все, что мы должны будем сделать, проверить, что протокол от URI действительно http (или https), когда мы возвратим список прокси, иначе мы только делегируем к значению по умолчанию один. Сделать это, мы должны будем, в конструкторе, сохранить ссылку на старое значение по умолчанию, потому что наш станет значением по умолчанию.

Таким образом, это начинает быть похожим на это:

public class MyProxySelector extends ProxySelector {
        ProxySelector defsel = null;
        MyProxySelector(ProxySelector def) {
                defsel = def;
        }
        
        public java.util.List<Proxy> select(URI uri) {
                if (uri == null) {
                        throw new IllegalArgumentException("URI can't be null.");
                }
                String protocol = uri.getScheme();
                if ("http".equalsIgnoreCase(protocol) ||
                        "https".equalsIgnoreCase(protocol)) {
                        ArrayList<Proxy> l = new ArrayList<Proxy>();
                        // Populate the ArrayList with proxies
                        return l;
                }
                if (defsel != null) {
                        return defsel.select(uri);
                } else {
                        ArrayList<Proxy> l = new ArrayList<Proxy>();
                        l.add(Proxy.NO_PROXY);
                        return l;
                }
        }
}

Сначала отметьте конструктора, который сохраняет ссылку на старый селектор по умолчанию. Во-вторых, заметьте проверку на недопустимый параметр в выборе () метод, чтобы уважать спецификации. Наконец, заметьте, как код подчиняется старому значению по умолчанию, если было один, когда необходимо. Конечно, в этом примере, я не детализировал, как заполнить ArrayList, поскольку это не особенно интересный, но полный код доступно в приложении, если Вам любопытно.

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

connectFailed() метод вызывает обработчик протокола всякий раз, когда это было не в состоянии соединиться с одним из прокси, возвращенных select() метод. Передают 3 параметра: URI, которого обработчик пытался достигнуть, который должен быть тем, используемым когда select() был вызван, SocketAddress из прокси, с которым обработчик пытался связаться и IOException, который был брошен, пытаясь соединиться с прокси. С той информацией мы только сделаем следующее: Если прокси будет в нашем списке, и это перестало работать 3 раза или больше, то мы только удалим это из нашего списка, удостоверяясь, что это не будет использоваться снова в будущем. Таким образом, код теперь:

public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
        if (uri == null || sa == null || ioe == null) {
                throw new IllegalArgumentException("Arguments can't be null.");
        }
        InnerProxy p = proxies.get(sa); 
        if (p != null) {
                if (p.failed() >= 3)
                        proxies.remove(sa);
        } else {
                if (defsel != null)
                        defsel.connectFailed(uri, sa, ioe);
        }
}

Довольно прямой не это. Снова мы должны проверить законность параметров (спецификации снова). Единственной вещью, которую мы действительно принимаем во внимание здесь, является SocketAddress, если это - один из прокси в нашем списке, тогда мы заключаем сделку с этим, иначе мы задерживаем, снова, к селектору по умолчанию.

Теперь, когда наша реализация, главным образом, полна, все, что мы должны сделать в приложении, должен зарегистрировать это, и мы делаемся:

public static void main(String[] args) {
        MyProxySelector ps = new MyProxySelector(ProxySelector.getDefault());
        ProxySelector.setDefault(ps);
        // rest of the application
}

Конечно, я упростил вещи немного ради ясности, в особенности Вы, вероятно, заметили, что я не делал большой ловли Исключения, но я уверен, что можно восполнить пробелы.

Нужно отметить, что и Плагин Java и Java Webstart действительно заменяют ProxySelector по умолчанию пользовательским, чтобы интегрировать лучше с базовой платформой или контейнером (как веб-браузер). Так имейте в виду, имея дело с ProxySelector, что значение по умолчанию каждый является обычно определенным для базовой платформы и для реализации JVM. Именно поэтому это - хорошая идея, обеспечивая пользовательскую, чтобы сохранить ссылку на более старый, поскольку мы прикончили вышеупомянутый пример, и используем его когда необходимо.

5) Заключение

Поскольку мы теперь установили J2SE 5.0, обеспечивает множество способов иметь дело с прокси. От очень простого (использование системы проксируют настройки) к очень гибкому (изменение ProxySelector, хотя для опытных разработчиков только), включая любезность выбора для каждого подключения Прокси-класса.

Приложение

Вот полный источник ProxySelector, который мы разработали в этой газете. Имейте в виду, что это было записано в образовательных целях только, и было поэтому сохранено довольно простым нарочно.

import java.net.*;
import java.util.List;
import java.util.ArrayList;
import java.util.HashMap;
import java.io.IOException;

public class MyProxySelector extends ProxySelector {
        // Keep a reference on the previous default
    ProxySelector defsel = null;
        
        /*
         * Inner class representing a Proxy and a few extra data
         */
        class InnerProxy {
        Proxy proxy;
                SocketAddress addr;
                // How many times did we fail to reach this proxy?
                int failedCount = 0;
                
                InnerProxy(InetSocketAddress a) {
                        addr = a;
                        proxy = new Proxy(Proxy.Type.HTTP, a);
                }
                
                SocketAddress address() {
                        return addr;
                }
                
                Proxy toProxy() {
                        return proxy;
                }
                
                int failed() {
                        return ++failedCount;
                }
        }
        
        /*
         * A list of proxies, indexed by their address.
         */
        HashMap<SocketAddress, InnerProxy> proxies = new HashMap<SocketAddress, InnerProxy>();

        MyProxySelector(ProxySelector def) {
          // Save the previous default
          defsel = def;
          
          // Populate the HashMap (List of proxies)
          InnerProxy i = new InnerProxy(new InetSocketAddress("webcache1.example.com", 8080));
          proxies.put(i.address(), i);
          i = new InnerProxy(new InetSocketAddress("webcache2.example.com", 8080));
          proxies.put(i.address(), i);
          i = new InnerProxy(new InetSocketAddress("webcache3.example.com", 8080));
          proxies.put(i.address(), i);
          }
          
          /*
           * This is the method that the handlers will call.
           * Returns a List of proxy.
           */
          public java.util.List<Proxy> select(URI uri) {
                // Let's stick to the specs. 
                if (uri == null) {
                        throw new IllegalArgumentException("URI can't be null.");
                }
                
                /*
                 * If it's a http (or https) URL, then we use our own
                 * list.
                 */
                String protocol = uri.getScheme();
                if ("http".equalsIgnoreCase(protocol) ||
                        "https".equalsIgnoreCase(protocol)) {
                        ArrayList<Proxy> l = new ArrayList<Proxy>();
                        for (InnerProxy p : proxies.values()) {
                          l.add(p.toProxy());
                        }
                        return l;
                }
                
                /*
                 * Not HTTP or HTTPS (could be SOCKS or FTP)
                 * defer to the default selector.
                 */
                if (defsel != null) {
                        return defsel.select(uri);
                } else {
                        ArrayList<Proxy> l = new ArrayList<Proxy>();
                        l.add(Proxy.NO_PROXY);
                        return l;
                }
        }
        
        /*
         * Method called by the handlers when it failed to connect
         * to one of the proxies returned by select().
         */
        public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
                // Let's stick to the specs again.
                if (uri == null || sa == null || ioe == null) {
                        throw new IllegalArgumentException("Arguments can't be null.");
                }
                
                /*
                 * Let's lookup for the proxy 
                 */
                InnerProxy p = proxies.get(sa); 
                        if (p != null) {
                                /*
                                 * It's one of ours, if it failed more than 3 times
                                 * let's remove it from the list.
                                 */
                                if (p.failed() >= 3)
                                        proxies.remove(sa);
                        } else {
                                /*
                                 * Not one of ours, let's delegate to the default.
                                 */
                                if (defsel != null)
                                  defsel.connectFailed(uri, sa, ioe);
                        }
     }
}

Oracle и/или его филиалы Авторское право © 1993, 2011, Oracle и/или его филиалы. Все права защищены.
Свяжитесь с Нами