Spec-Zone .ru
спецификации, руководства, описания, API
|
В сегодняшних сетевых средах, особенно корпоративных, разработчики приложений должны иметь дело с прокси почти так часто как системные администраторы. В некоторых случаях приложение должно использовать настройки системного значения по умолчанию в других случаях, оно будет мы хотеть иметь очень жесткий контроль над тем, что проходит через, какой прокси, и, где-нибудь в середине, большинство приложений будет счастливо делегировать решение своим пользователям тем, если их с GUI, чтобы установить настройки прокси, как имеет место в большинстве браузеров.
В любом случае платформа разработки, как Java, должна обеспечить механизмы, чтобы иметь дело с этими прокси, которые и мощны и гибки. К сожалению, до недавнего времени, платформа Java не была очень гибка в том отделе. Но все, что изменилось в J2SE 5.0 как новый API, было представлено, чтобы адресовать этот недостаток, и цель этой бумаги состоит в том, чтобы обеспечить всестороннее объяснение всех этих API и механизмов, старых, которые все еще допустимы, так же как новые.
Вплоть до J2SE 1.4 системных свойства были единственным способом установить прокси-серверы в пределах сетевого API Java для любого из обработчиков протокола. Чтобы сделать вопросы более сложными, имена этих свойств изменились от одного выпуска до другого, и некоторые из них являются теперь устаревшими, даже если они все еще поддерживаются для пользы совместимости.
Главное ограничение использования системных свойств - то, что они "все или ничего" переключатель. Означая, что, как только прокси был установлен для определенного протокола, он будет влиять на все соединения для того протокола. Это - широкое поведение VM.
Есть 2 основных способа установить системные свойства:
System.setProperty(String, String)
метод, принятие, конечно что у Вас есть разрешение, чтобы сделать так.Теперь, давайте смотреть, протокол протоколом, в свойствах, которые можно использовать, чтобы установить прокси. Все прокси определяются именем хоста и номером порта. Позже является дополнительным как, если это не будет определено, то стандартный порт значения по умолчанию будет использоваться.
Есть 3 свойства, которые можно установить, чтобы определить прокси, который будет использоваться http обработчиком протокола:
http.proxyHost
: имя хоста прокси-сервераhttp.proxyPort
: номер порта, значение по умолчанию, являющееся 80.http.nonProxyHosts
Список:a узлов, которые должны быть достигнуты непосредственно, обходя прокси. Это - список образцов, разделенных '|'. Образцы могут запуститься или закончиться '*' для подстановочных знаков. Любой узел, соответствующий один из этих образцов, будет достигнут посредством прямой связи вместо через прокси.Давайте смотреть на несколько примеров, предполагающих, что мы пытаемся выполнить основной метод GetURL class:
$ java -Dhttp.proxyHost=webcache.example.com GetURL
Все http соединения пройдут через прокси-сервер в webcache.example.com
слушание на порту 80 (мы не определяли порта, таким образом, значение по умолчанию каждый используется).
$ java -Dhttp.proxyHost=webcache.example.com -Dhttp.proxyPort=8080 -Dhttp.nonProxyHosts=”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”, таким образом, на все потоки влияют. Что означает, что код в одном потоке, как побочный эффект, мог представить код в другом недействующем потоке.
У https (http по SSL) обработчик протокола есть свой собственный набор свойств:
Поскольку Вы, вероятно, предположили, что они работают тем же самым способом их http дубликатами, таким образом, мы не будем проникать в большое количество деталей кроме упомянуть, что номер порта значения по умолчанию, на сей раз, 443 и что для "не проксируют узлы" список, обработчик протокола HTTPS будет использовать то же самое в качестве http обработчика (то есть. http.nonProxyHosts
).
Настройки для обработчика протокола FTP следуют за теми же самыми правилами что касается http, единственная разница - то, что каждое имя свойства теперь снабжается префиксом'ftp
.' вместо'http.
'
Поэтому системные свойства:
ftp.proxHost
ftp.proxyPort
ftp.nonProxyHosts
Отметьте, что на сей раз есть отдельное свойство для "не, проксируют узлы" список. Кроме того, что касается 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.
Протокол SOCKS, как определено в RFC 1928, служит основой для клиент-серверных приложений, чтобы безопасно пересечь брандмауэр и в TCP и в уровне UDP. В этом смысле это - намного больше обобщения чем высокоуровневые прокси (как HTTP или FTP определенные прокси). J2SE 5.0 оказывает поддержку SOCKS для клиентских сокетов TCP.
Есть 2 системных свойства, связанные с SOCKS:
socksProxyHost
для имени хоста прокси-сервера SOCKSsocksProxyPort
для номера порта, значение по умолчанию, являющееся 1080Отметьте, что нет никакой точки ('. ') после префикса на сей раз. Это по историческим причинам и гарантировать обратную совместимость. Как только прокси 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 был определен, то тот прокси использовался бы вместо этого.
Как мы видели, системные свойства мощны, но не гибки. "Все или ничего" поведение справедливо считало слишком серьезным ограничением большинство разработчиков. Именно поэтому было решено представить новый, более гибкий, API в J2SE 5.0 так, чтобы было возможно иметь соединение базируемые настройки прокси.
Ядро этого нового API является Прокси class, который представляет определение прокси, обычно тип (http, носки) и адрес сокета. Есть, с J2SE 5.0, 3 возможных типов:
DIRECT
который представляет прямую связь, или отсутствие прокси.HTTP
который представляет прокси, используя протокол HTTP.SOCKS
который представляет прокси, используя или SOCKS v4 или v5.Так, чтобы создать объект прокси HTTP, который что Вы вызвали бы:
SocketAddress addr = new InetSocketAddress("webcache.example.com", 8080); Proxy proxy = new Proxy(Proxy.Type.HTTP, addr);
Помните, этот новый объект прокси представляет определение прокси, ничто больше. Как мы используем такой объект? Новое openConnection()
метод был добавлен к URL class и берет Прокси в качестве параметра, это работает тот же самый путь как 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).
Как можно видеть с 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 состоит в том, что это - plugable! Что означает, что, если у Вас есть потребности, которые не удовлетворяются значением по умолчанию один, можно записать замену для него и включить его!
Так, каков ProxySelector? Давайте смотреть на определение class:
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 является абстрактный class с 2 статическими методами, чтобы установить, или добраться, реализация по умолчанию, и 2 метода экземпляра, которые будут использоваться обработчиками протокола, чтобы определить, какой прокси использовать или уведомить, что прокси, кажется, недостижим. Если Вы хотите предоставить своему собственному ProxySelector, все, что необходимо сделать, расширяют этот class, обеспечивают, реализация для этих 2 методов экземпляра тогда вызывают ProxySelector.setDefault () передача экземпляра Вашего нового class как параметр. В этой точке обработчики протокола, как 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, поскольку это не особенно интересный, но полный код доступно в приложении, если Вам любопытно.
Как это, class является неполным, так как мы не обеспечивали реализацию для 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. Именно поэтому это - хорошая идея, обеспечивая пользовательскую, чтобы сохранить ссылку на более старый, поскольку мы прикончили вышеупомянутый пример, и используем его когда необходимо.
Поскольку мы теперь установили J2SE 5.0, обеспечивает множество способов иметь дело с прокси. От очень простого (использование системы проксируют настройки) к очень гибкому (изменение ProxySelector, хотя для опытных разработчиков только), включая любезность выбора для каждого подключения Прокси class.
Вот полный источник 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); } } }