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).
Re-executing the example will give out this WSDL:
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 ;-).