It seems to me that Web Services don’t receive much love from Rubyists. In fact, of the two Ruby projects I know that add Web Service support (SOAP4R and ActionWebService), both appear to be inactive. Someone might say that if Web Services are a must, then avoid Ruby or put an integration layer between your Ruby application and the client/service. From my experience, life is not always that simple and these solutions might not be applicable.
The Java ecosystem has a popular and well-supported open source project that is used to build SOAP Web Services and clients. This project is called Apache CXF. On one fine sunny day I asked myself: “Wouldn’t it be great if I could publish a Web Service from Ruby using Apache CXF?”. Almost immediately I put that thought away. Trying to integrate a Java library into Ruby is, well, hard in my books. But then JRuby popped into my mind. JRuby is the Ruby language implemented in Java. This means that Ruby and Java objects talk to each other with relative ease.
Seeing the potential in the idea, last week I set about developing a JRuby wrapper gem for CXF. I must admit it was more challenging than I thought but at the end I was happy with the results. The bulk of the work was customising the Aegis data binder so that it could map RubyObject instances.
The first step to using the gem is installing it:
A code example is in order here:
Publishing the above class as a Web Service means requiring the gem and including the module CXF::WebServiceServlet:
Including WebServiceServlet causes the class to become a regular Java servlet. This implies that any servlet container can load the Web Service. For this example, I’ll load the Web Service using an embedded Jetty:
Running the example requires two libraries to be available in the Java classpath: CXF 2.7.6 and Jetty 8.
Accessing the URL http://localhost:8080/hello-world?wsdl with a browser will display the following WSDL:
You’ll note that the operations are missing from the WSDL. This is because I didn’t tell CXF to expose any of the methods in the class HelloWorld as Web Service operations. Let me do that now:
expose tells CXF to publish the method denoted by the first argument (i.e., :say_hello). The second argument in expose is a map. It should have at a minimum the following entries:
- expects - maps to an ordered list of hashes where each hash corresponds to a method parameter and its expected type.
- returns - maps to the expected return type (e.g., :string).
The gem supports various options to customise the WSDL. For instance, the service name and namespace can be changed:
The complete list of options is found in the project repository’s README file .
Till now I’ve assumed that a Web Service operation will only accept simple types. In the real world we’re more likely to be using complex types:
I’ve added two classes in the example: Animal and Person. It is necessary to include the CXF::ComplexType module so that CXF can derive an XML schema from these classes and embed the schema in the WSDL. A complex type element is declared using the method member. A member needs at least a name for the element and its type. You could also declare whether a property is required as seen in the member pet. The required option defaults to true if not specified.
Note that now say_hello and give_age are expecting a Person object instead of primitive types and they are accessing the object via accessors. Behind the scenes the gem creates an accessor for each member that is declared.
I hope I’ve given you enough info to get started out with the gem. My plan is maintain JRuby CXF as I believe it could be useful for those who aren’t happy with the current alternatives. Of course, if you find an issue with the gem, I’d be more than happy to accept code contributions ;-).