Статьи

Бегущий Мул на Openshift – Часть 2

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