Automatic Proxy Selection for Mule ESB Webservices - codecentric AG Blog

:

When configuring mule, you might find out that some of your services need to call external parties. Depending on your deployment scenario there needs to be a proxy server involved. However this might change for various test stages, some need, some don’t, some need them only for specific domains and like that.
To reduce configuration overhead we have created an automatic proxy selector used for our services. Because I think its neat, I want to share it with you.

<https:connector name="autoProxy">
  <spring:property name="proxyHostname">
    <spring:bean class="org.springframework.jndi.JndiObjectFactoryBean">
      <spring:property name="jndiName" value="/jndi/config/proxyHost" />
      <spring:property name="defaultObject" value="" />
    </spring:bean>
  </spring:property>
  <spring:property name="proxyPort">
    <spring:bean class="org.springframework.jndi.JndiObjectFactoryBean">
      <spring:property name="jndiName" value="/jndi/config/proxyPort" />
      <spring:property name="defaultObject" value="0" />
    </spring:bean>
  </spring:property>
  <service-overrides dispatcherFactory="de.codecentric.mule.HttpAutoProxyMessageDispatcherFactory" />
  <https:tls-key-store path="/mule.keystore" keyPassword="changeit" storePassword="changeit"/>
  <https:tls-server path="/mule.keystore" storePassword="changeit"/>
</https:connector>

<https:connector name="autoProxy"> <spring:property name="proxyHostname"> <spring:bean class="org.springframework.jndi.JndiObjectFactoryBean"> <spring:property name="jndiName" value="/jndi/config/proxyHost" /> <spring:property name="defaultObject" value="" /> </spring:bean> </spring:property> <spring:property name="proxyPort"> <spring:bean class="org.springframework.jndi.JndiObjectFactoryBean"> <spring:property name="jndiName" value="/jndi/config/proxyPort" /> <spring:property name="defaultObject" value="0" /> </spring:bean> </spring:property> <service-overrides dispatcherFactory="de.codecentric.mule.HttpAutoProxyMessageDispatcherFactory" /> <https:tls-key-store path="/mule.keystore" keyPassword="changeit" storePassword="changeit"/> <https:tls-server path="/mule.keystore" storePassword="changeit"/> </https:connector>

As you can see we configured a Connector which creates this MessageDispatcherFactory, it passes the configuration obtained from JNDI to it and also adds a keystore for ssl connections to it (with the great default password changeit :-))
Using it is then straightforward:

  <outbound-endpoint address="https://external.service/endpoint" synchronous="true" connector-ref="autoProxy">

<outbound-endpoint address="https://external.service/endpoint" synchronous="true" connector-ref="autoProxy">

The Factory itself is dead simple:

public class HttpAutoProxyMessageDispatcherFactory
  extends AbstractMessageDispatcherFactory {
  public MessageDispatcher create(OutboundEndpoint endpoint) throws MuleException {
    return new HttpAutoProxyMessageDispatcher(endpoint);
  }
}

public class HttpAutoProxyMessageDispatcherFactory extends AbstractMessageDispatcherFactory { public MessageDispatcher create(OutboundEndpoint endpoint) throws MuleException { return new HttpAutoProxyMessageDispatcher(endpoint); } }

The HttpAutoProxyMessageDispatcher implementation is easy was well. And contains a few hardcoded hosts that should not be proxied. Feel free to improve that part:

public class HttpAutoProxyMessageDispatcher
  extends HttpClientMessageDispatcher {
 
  private final boolean hasProxy;
  private final String proxyHost;
  private final int proxyPort;
 
  public HttpAutoProxyMessageDispatcher(OutboundEndpoint endpoint) {
    super(endpoint);
    this.proxyHost = ((HttpConnector) endpoint.getConnector()).getProxyHostname();
    this.proxyPort = ((HttpConnector) endpoint.getConnector()).getProxyPort();
    this.hasProxy = StringUtils.isNotBlank(proxyHost);
  }
 
  @Override
  protected HostConfiguration getHostConfig(URI uri) throws URISyntaxException {
    String host = uri.getHost();
    HostConfiguration config = new HostConfiguration();
    config.setHost(host, uri.getPort(), Protocol.getProtocol(uri.getScheme().toLowerCase()));
    if (hasProxy && !isLocalhost(host)) {
      config.setProxy(proxyHost, proxyPort);
    }
    return config;
  }
 
  private boolean isLocalhost(String host) {
    return "localhost".equals(host) || "127.0.0.1".equals(host);
  }
 
}

public class HttpAutoProxyMessageDispatcher extends HttpClientMessageDispatcher {private final boolean hasProxy; private final String proxyHost; private final int proxyPort;public HttpAutoProxyMessageDispatcher(OutboundEndpoint endpoint) { super(endpoint); this.proxyHost = ((HttpConnector) endpoint.getConnector()).getProxyHostname(); this.proxyPort = ((HttpConnector) endpoint.getConnector()).getProxyPort(); this.hasProxy = StringUtils.isNotBlank(proxyHost); }@Override protected HostConfiguration getHostConfig(URI uri) throws URISyntaxException { String host = uri.getHost(); HostConfiguration config = new HostConfiguration(); config.setHost(host, uri.getPort(), Protocol.getProtocol(uri.getScheme().toLowerCase())); if (hasProxy && !isLocalhost(host)) { config.setProxy(proxyHost, proxyPort); } return config; }private boolean isLocalhost(String host) { return "localhost".equals(host) || "127.0.0.1".equals(host); }}

When you applied this pattern you only need to make sure JNDI information regarding the proxy is correct on each environment, and the mule configuration will automatically adapt to it.

Update: Mule 3.2

Recent Versions of Mule made it even easier to implement the HttpAutoProxyMessageDispatcher:

public class HttpAutoProxyMessageDispatcher extends
    HttpClientMessageDispatcher implements MessageDispatcher {
 
  public HttpAutoProxyMessageDispatcher(OutboundEndpoint endpoint) {
    super(endpoint);
  }
 
  @Override
  protected HostConfiguration getHostConfig(URI uri) throws Exception {
    Protocol protocol = Protocol.getProtocol(uri.getScheme().toLowerCase());
    String host = uri.getHost();
    int port = uri.getPort();
    HostConfiguration config = new HostConfiguration();
    config.setHost(host, port, protocol);
    if (!isLocalhost(host) && StringUtils.isNotBlank(connector.getProxyHostname())) {
      config.setProxy(connector.getProxyHostname(), connector.getProxyPort());
    }
    return config;
  }
 
  private boolean isLocalhost(String host) {
    return "localhost".equals(host) || "127.0.0.1".equals(host); 
  }
}

public class HttpAutoProxyMessageDispatcher extends HttpClientMessageDispatcher implements MessageDispatcher {public HttpAutoProxyMessageDispatcher(OutboundEndpoint endpoint) { super(endpoint); }@Override protected HostConfiguration getHostConfig(URI uri) throws Exception { Protocol protocol = Protocol.getProtocol(uri.getScheme().toLowerCase()); String host = uri.getHost(); int port = uri.getPort(); HostConfiguration config = new HostConfiguration(); config.setHost(host, port, protocol); if (!isLocalhost(host) && StringUtils.isNotBlank(connector.getProxyHostname())) { config.setProxy(connector.getProxyHostname(), connector.getProxyPort()); } return config; }private boolean isLocalhost(String host) { return "localhost".equals(host) || "127.0.0.1".equals(host); } }