Friday, 23 August 2013

Running Mule on Openshift - Part 2

A little while back I wrote a post on "Running Mule Natively on OpenShift" - http://ryandcarter.blogspot.co.uk/2013/03/natively-running-mule-on-openshift.html. This post showed some workarounds for a few conflicting "features" of both products and showed how to run a simple Hello World app. Since then both products have moved up major versions with both fixing the need for some of the previous work-arounds and also introducing some new ones. I have also had time to expand on a simple Hello World app and start trying out some other Mule components which also require some hacks to get up and running. In this article I'll do a quick recap of getting the app up and running and then show how to work with these new features.

HTTP Server Binding

In the previous post we tried to deploy the following app to Mule 3.3.1:

The only thing to take note of here is that we are using the ${OPENSHIFT_DIY_IP} environment variable which has changed from the previous ${OPENSHIFT_INTERNAL_IP}. This is the suggested IP address for creating socket connections on localhost. Typical values like "localhost", "127.0.0.1" and "0.0.0.0" are all locked down.

However, if you try using this environment variable as your host you will get an error similar to the following:

Permission denied (java.net.BindException) java.net.PlainSocketImpl:-2 (null) 2. Failed to bind to uri "http://127.8.109.1:8080"

After digging through the source, there is a slight issue with Mules' TCP transport for versions < 3.4.

Here, the internal IP is a loopback address, so Mule forces it down the path of creating a TCP socket that listens on all interfaces for that port. Fortunately there is already af fix in the 3.4 release which is now GA. If you want to use Mule versions < 3.4 then you will need to modify the TCP transport as detailed in the previous post.

HTTP Client Binding

The previous app just focused on exposing an inbound endpoint over HTTP and didn't look at using outbound endpoints. Take the following modified Hello World application:

Feeling a bit european, this modified app simply contacts an external HTTP API to request "Hello World" in Italian. Here, the http:outbound-endpoint shouldn't need to bind to any internal port as it's not necessary for client TCP connections. However, when running the application you will still get: Permission denied (java.net.BindException). Under the hood, Mule uses the Apache commons-httpclient3 library for client connections which in turn uses uses it's DefaultSocketFactory class to create java.net.Socket objects. The java.net.Socket class has several different constructors, and commons-httpclient by default will call:

public Socket(String host, int port, InetAddress localAddr, int localPort) throws IOException...

Here, for the "localAddr" argument, null is passed. When null is passed for the "localAddr" argument, the java.net.Socket will use InetAddress.anyLocalAddress(); This will perform a bind on the localAddr/port which is not necessary for client TCP connections, although under most circumstances is harmless. Except on OpenShift where this is specifically locked down and results in the java.net.BindException being thrown.

This is rectified in later versions of the library which are now refactored into the Apache HttpComponents library and the HttpClient Inerfaces have changed signigicantly resulting in a non-trivial upgrade path. There is a solution however, with thanks to mikebennettjackbe - https://www.openshift.com/forums/openshift/commons-httpclient-permission-denied to create a custom SocketFactory to override the creation of the Socket and not bind on any local address:

To make commons-httpclient use the new OSProtocolSocketFactory instead of its own, we need to regiser it when he app starts up. One approach to this, is we can implement the MuleNotificationListener interface:

This class allows us to check when the Mule context is initialised and register the new Socket factory for the http and/or the https protocols. After creating the above class, you just need to define it as a bean within your Mule config, like so:

And that's it. You should now be able to use http outbound endpoints and use connectors that rely on the Apache commons-httpclient3 library such as the Twitter Cloud Connector. There are more than likely other transports or connectors that have issues around this area so feel free to comment or raise an issue or pull request on Github. Full project can be found here: https://github.com/ryandcarter/mule-diy-cartridge

No comments:

Post a Comment