Некоторое время назад я написал статью «Запуск мула в OpenShift» — http://ryandcarter.blogspot.co.uk/2013/03/natively-running-mule-on-openshift.html . В этом посте показаны некоторые обходные пути для нескольких противоречивых «функций» обоих продуктов и показано, как запустить простое приложение Hello World. С тех пор оба продукта перешли на более крупные версии, в которых устранены некоторые из предыдущих решений, а также введены некоторые новые. У меня также было время расширить простое приложение Hello World и начать пробовать некоторые другие компоненты Mule, которые также требуют некоторых хаков для запуска и запуска. В этой статье я кратко расскажу о том, как запустить и запустить приложение, а затем покажу, как работать с этими новыми функциями.
Привязка HTTP-сервера
В предыдущем посте мы пытались развернуть следующее приложение в Mule 3.3.1:
<flow name="HelloWorldFlow"> <http:inbound-endpoint exchange-pattern="request-response" host="${OPENSHIFT_DIY_IP}" port="8080" /> <set-payload value="Hello World!" /> </flow>
Единственное, на что следует обратить внимание, это то, что мы используем переменную среды $ {OPENSHIFT_DIY_IP}, которая изменилась по сравнению с предыдущим $ {OPENSHIFT_INTERNAL_IP}. Это рекомендуемый IP-адрес для создания соединений сокетов на локальном хосте. Типичные значения, такие как «localhost», «127.0.0.1» и «0.0.0.0», все заблокированы.
Однако, если вы попытаетесь использовать эту переменную среды в качестве хоста, вы получите ошибку, подобную следующей:
В доступе отказано (java.net.BindException) java.net.PlainSocketImpl: -2 (null) 2. Не удалось связать с URI «http://127.8.109.1:8080»
После просмотра исходного кода, существует небольшая проблема с TCP-транспортом Mules для версий <3.4.
public ServerSocket createServerSocket(URI uri, int backlog, Boolean reuse) throws IOException { String host = StringUtils.defaultIfEmpty(uri.getHost(), "localhost"); InetAddress inetAddress = InetAddress.getByName(host); if (inetAddress.equals(InetAddress.getLocalHost()) || inetAddress.isLoopbackAddress() || host.trim().equals("localhost")){ return createServerSocket(uri.getPort(), backlog, reuse); } else { return createServerSocket(inetAddress, uri.getPort(), backlog, reuse); } } public ServerSocket createServerSocket(InetAddress address, int port, int backlog, Boolean reuse) throws IOException { return configure(new ServerSocket(), reuse, new InetSocketAddress(address, port), backlog); } public ServerSocket createServerSocket(int port, int backlog, Boolean reuse) throws IOException { return configure(new ServerSocket(), reuse, new InetSocketAddress(port), backlog); }
Здесь внутренний IP является адресом обратной связи, поэтому Мул вынуждает его создать путь к сокету TCP, который прослушивает все интерфейсы для этого порта. К счастью, в версии 3.4 уже есть исправление, которое теперь GA. Если вы хотите использовать версии Mule <3.4, вам нужно будет изменить транспорт TCP, как описано в предыдущем посте.
Привязка клиента HTTP
Предыдущее приложение просто фокусировалось на предоставлении входящей конечной точки через HTTP и не рассматривало использование исходящих конечных точек. Возьмите следующее модифицированное приложение Hello World:
<?xml version="1.0" encoding="UTF-8"?> <mule xmlns:https="http://www.mulesoft.org/schema/mule/https" xmlns:http="http://www.mulesoft.org/schema/mule/http" xmlns="http://www.mulesoft.org/schema/mule/core" xmlns:doc="http://www.mulesoft.org/schema/mule/documentation" xmlns:spring="http://www.springframework.org/schema/beans" version="EE-3.4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.mulesoft.org/schema/mule/http http://www.mulesoft.org/schema/mule/http/current/mule-http.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-current.xsd http://www.mulesoft.org/schema/mule/core http://www.mulesoft.org/schema/mule/core/current/mule.xsd"> <spring:bean name="SocketFactoryOverrider" id="SocketFactoryOverrider" class="org.ryandcarter.util.SocketFactoryOverrider" /> <flow name="HelloWorldFlow"> <http:inbound-endpoint exchange-pattern="request-response" host="${OPENSHIFT_DIY_IP}" port="8080" /> <http:outbound-endpoint exchange-pattern="request-response" address="http://mymemory.translated.net/api/get?q=Hello%20World!&langpair=en%7Cit" /> </flow> </mule>
Чувствуя себя немного по-европейски, это модифицированное приложение просто связывается с внешним HTTP API, чтобы запросить «Hello World» на итальянском языке. Здесь http: outbound-endpoint не нужно привязывать к какому-либо внутреннему порту, так как он не нужен для клиентских TCP-соединений. Однако при запуске приложения вы все равно получите: Отказано в доступе (java.net.BindException). Под капотом Мул использует библиотеку Apache commons-httpclient3 для клиентских подключений, которая, в свою очередь, использует класс DefaultSocketFactory для создания объектов java.net.Socket. Класс java.net.Socket имеет несколько различных конструкторов, и commons-httpclient по умолчанию вызовет:
public Socket (String host, int port, InetAddress localAddr, int localPort) выдает IOException …
Здесь для аргумента localAddr передается значение null. Когда для аргумента localAddr передается значение null, java.net.Socket будет использовать InetAddress.anyLocalAddress (); Это выполнит привязку к localAddr / порту, которая не нужна для клиентских TCP-соединений, хотя в большинстве случаев безвредна. За исключением OpenShift, где это специально заблокировано и приводит к созданию исключения java.net.BindException.
Это исправлено в более поздних версиях библиотеки, которые теперь реорганизованы в библиотеку Apache HttpComponents, а интерфейсы HttpClient значительно изменились, что привело к нетривиальному пути обновления. Однако существует решение, благодаря mikebennettjackbe —
https://www.openshift.com/forums/openshift/commons-httpclient-permission-denied для создания пользовательского SocketFactory, который переопределяет создание Socket и не привязывается ни к какому локальному адрес:
package com.jackbe.util; import java.io.IOException; import java.net.InetAddress; import java.net.Socket; import java.net.UnknownHostException; import org.apache.commons.httpclient.ConnectTimeoutException; import org.apache.commons.httpclient.params.HttpConnectionParams; import org.apache.commons.httpclient.protocol.ProtocolSocketFactory; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; public class OSProtocolSocketFactory implements ProtocolSocketFactory { private static Log log = LogFactory.getLog(OSProtocolSocketFactory.class); public OSProtocolSocketFactory() { } @Override public Socket createSocket(String host, int port, InetAddress localAddress, int localPort) throws IOException, UnknownHostException { if (log.isDebugEnabled()) { log.debug("createSocket called. host = " + host + ", port = " + port + ", ignoring localAddress = " + ((localAddress != null) ? localAddress.toString() : "null") + ", ignoring localPort = " + localPort); } Socket socket = null; try { socket = new Socket(host, port); log.debug("Socket created"); } catch (IOException e) { log.error("Error creating socket: " + e.getMessage()); throw e; } return socket; } @Override public Socket createSocket(String host, int port, InetAddress localAddress, int localPort, HttpConnectionParams params) throws IOException, UnknownHostException, ConnectTimeoutException { log.debug("createSocket called with HttpConnectionParams -- ignoring the timeout value and proceeding"); return this.createSocket(host, port, localAddress, localPort); } @Override public Socket createSocket(String host, int port) throws IOException, UnknownHostException,IOException { log.debug("createSocket called with just host and port. proceeding.."); return this.createSocket(host, port, null, 0); } }
Чтобы commons-httpclient использовал новый OSProtocolSocketFactory вместо своего собственного, нам нужно зарегистрировать его при запуске приложения. Одним из подходов к этому является то, что мы можем реализовать интерфейс MuleNotificationListener:
package org.ryandcarter.util; import org.apache.commons.httpclient.protocol.Protocol; import org.apache.log4j.Logger; import org.mule.api.MuleContext; import org.mule.api.context.MuleContextAware; import org.mule.api.context.notification.MuleContextNotificationListener; import org.mule.context.notification.MuleContextNotification; public class SocketFactoryOverrider implements MuleContextNotificationListener<MuleContextNotification>, MuleContextAware { private MuleContext context; private final static Logger log = Logger.getLogger(SocketFactoryOverrider.class); @Override public void onNotification(MuleContextNotification notification) { if (notification.getAction() == MuleContextNotification.CONTEXT_STARTED) { log.debug("Initializing httpclient protcol with overridden SocketFactory"); Protocol.registerProtocol("http", new Protocol("http", new OSProtocolSocketFactory(), 80)); Protocol.registerProtocol("https", new Protocol("https", new OSProtocolSocketFactory(), 80)); } } @Override public void setMuleContext(MuleContext context) { this.context = context; } public MuleContext getContext() { return context; } }
Этот класс позволяет нам проверять, когда инициализируется контекст Mule, и регистрировать новую фабрику сокетов для протоколов http и / или https. После создания вышеуказанного класса вам просто нужно определить его как bean-компонент в вашей конфигурации Mule, например:
<?xml version="1.0" encoding="UTF-8"?> <mule xmlns:https="http://www.mulesoft.org/schema/mule/https" xmlns:http="http://www.mulesoft.org/schema/mule/http" xmlns="http://www.mulesoft.org/schema/mule/core" xmlns:doc="http://www.mulesoft.org/schema/mule/documentation" xmlns:spring="http://www.springframework.org/schema/beans" version="EE-3.4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.mulesoft.org/schema/mule/http http://www.mulesoft.org/schema/mule/http/current/mule-http.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-current.xsd http://www.mulesoft.org/schema/mule/core http://www.mulesoft.org/schema/mule/core/current/mule.xsd"> <spring:bean name="SocketFactoryOverrider" id="SocketFactoryOverrider" class="org.ryandcarter.util.SocketFactoryOverrider" /> <flow name="HelloWorldFlow"> <http:inbound-endpoint exchange-pattern="request-response" host="${OPENSHIFT_DIY_IP}" port="8080" /> <http:outbound-endpoint exchange-pattern="request-response" address="http://mymemory.translated.net/api/get?q=Hello%20World!&langpair=en|it" /> </flow> </mule>
Вот и все. Теперь вы сможете использовать исходящие конечные точки http и использовать коннекторы, использующие библиотеку Apache commons-httpclient3, такие как Twitter Cloud Connector. Скорее всего, есть другие транспорты или соединители, которые имеют проблемы в этой области, поэтому не стесняйтесь комментировать, поднимать проблемы или выдвигать запросы на Github. Полный проект можно найти здесь: https://github.com/ryandcarter/mule-diy-cartridge