Некоторое время назад я написал статью «Запуск мула в 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