Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / Ruby

Web Services in Ruby, Python and Java

5.00/5 (1 vote)
10 Aug 2011CPOL4 min read 23.4K  
Web Services in Ruby, Python and Java

I had to prepare some examples to show that web services are interoperable. So I created a simple web service in Java using Metro and launched it on Tomcat. Then, I tried to consume them using Python and Ruby. Here’s how it all finished…

Web Service in Java

I started with a simple web service in Java:

Java
package com.wordpress.jdevel.ws;

import java.io.File;
import java.io.FileFilter;
import java.io.FilenameFilter;
import java.util.ArrayList;
import java.util.List;
import javax.jws.WebService;
import javax.jws.WebMethod;
import javax.jws.WebParam;

@WebService(serviceName = "Music")
public class Music {

    private static final File FOLDER = new File("D:/TEMP/SONGS");

    @WebMethod(operationName = "listSongs")
    public Song[] listSongs(@WebParam(name = "artist") String artist) {

        List<Song> songs = new ArrayList<Song>();
        System.out.println("ARTIST: " + artist);
        if (artist != null) {
            File folder = new File(FOLDER, artist);
            if (folder.exists() && folder.isDirectory()) {
                File[] listFiles = folder.listFiles(new FilenameFilter() {

                    public boolean accept(File dir, String name) {
                        return name.toUpperCase().endsWith(".MP3");
                    }
                });

                for (File file : listFiles) {
                    String fileName = file.getName();
                    String author = file.getParentFile().getName();
                    int size = (int) (file.length() / 1048576); //Megabytes
                    Song song = new Song(fileName, author, size);
                    songs.add(song);
                }
            }
        }

        return songs.toArray(new Song[songs.size()]);
    }

    @WebMethod(operationName = "listArtists")
    public String[] listArtists() {
        File[] folders = getFolders(FOLDER);
        List<String> artists = new ArrayList<String>(folders.length);
        for (File folder : folders) {
            artists.add(folder.getName());
        }
        return artists.toArray(new String[artists.size()]);
    }

    private File[] getFolders(File parent) {
        FileFilter filter = new FileFilter() {

            public boolean accept(File pathname) {
                return pathname.isDirectory();
            }
        };

        File[] folders = parent.listFiles(filter);

        return folders;
    }

    public static void main(String[] args) {
        Music listFiles = new Music();
        String[] artists = listFiles.listArtists();
        System.out.println("Artists: " + artists);
        for (String artist : artists) {
            Song[] listSongs = listFiles.listSongs(artist);
            for (Song song : listSongs) {
                System.out.println(song.getArtist() + " : " + 
                song.getFileName() + " : " + song.getSize() + "MB");
            }
        }
    }
}

I also needed a simple bean to get some more complex types:

Java
package com.wordpress.jdevel.ws;

import java.io.Serializable;

public class Song implements Serializable {
    String fileName;
    String artist;
    int size;

    public Song() {
    }

    public Song(String fileName, String artist, int size) {
        this.fileName = fileName;
        this.artist = artist;
        this.size = size;
    }

    public String getArtist() {
        return artist;
    }

    public void setArtist(String artist) {
        this.artist = artist;
    }

    public String getFileName() {
        return fileName;
    }

    public void setFileName(String fileName) {
        this.fileName = fileName;
    }

    public int getSize() {
        return size;
    }

    public void setSize(int size) {
        this.size = size;
    }
}

It’s just listing all sub-directories in hardcoded FOLDER directory and treats it as list of artists in music collection. Then you can execute listSongs method and get list of mp3 files in artist sub-folder. To make it a web service, all you have to do is annotate class with @WebService(serviceName = "Music") and every method you want to expose as web service operation has to be marked with @WebMethod(operationName = "listArtists"). This should be all if you’re deploying it on GlassFish, but I’ve used Tomcat, so 3 more steps were needed:

  1. Add Metro 2.0 jars to WEB-INF/lib
  2. Add Metro servlet and listener to web.xml:
    XML
    <listener>
            <listener-class>
            com.sun.xml.ws.transport.http.servlet.WSServletContextListener</listener-class>
        </listener>
        <servlet>
            <servlet-name>Music</servlet-name>
            <servlet-class>com.sun.xml.ws.transport.http.servlet.WSServlet</servlet-class>
            <load-on-startup>1</load-on-startup>
        </servlet>
        <servlet-mapping>
            <servlet-name>Music</servlet-name>
            <url-pattern>/Music</url-pattern>
        </servlet-mapping>

    You probably shouldn’t change anything here. Just paste it to your web.xml in web-app node.

  3. Add sun-jaxws.xml file to WEB-INF with endpoint declaration:
    XML
    <?xml version="1.0" encoding="UTF-8"?>
    <endpoints version="2.0" 
    xmlns="http://java.sun.com/xml/ns/jax-ws/ri/runtime">
      <endpoint implementation="com.wordpress.jdevel.ws.Music" 
      name="Music" url-pattern="/Music"/>
    </endpoints>
    • implementation has to match your @WebService class
    • name has to match serviceName in @WebService annotation
    • url-pattern has to match url-pattern you have declared in servlet mapping

There should also be no need to edit these XML files if you create it in NetBeans.

Now start Tomcat and deploy your app. You should be able to access your service via something like http://localhost:8080/WSServer/Music and see something like this:

WSDL will be accessible via http://localhost:8080/WSServer/Music?wsdl schema for complex types: http://localhost:8080/WSServer/Music?xsd=1
If you got this working, you can start with...

Python Client

I’ve started Googling for some nice web services library for Python and found Suds. I haven’t really used anything like this. Implementing WS client took me about 15 minutes. Supporting complex types of course and the last time I’ve been using Python for something bigger than 5 lines was about 3 years ago. You really have to try it out. So here’s the code:

Python
import suds

class Client:
    def __init__(self):
        self.client = suds.client.Client("http://localhost:8080/WSServer/Music?wsdl")

    def get_artists(self):
        return self.client.service.listArtists()

    def get_songs(self, artist):
        return self.client.service.listSongs(artist)

if(__name__ == "__main__"):
    client = Client()
    artists = client.get_artists()
    for artist in artists:
        print artist
        songs = client.get_songs(artist)
        for song in songs:
            print "\t%s : %s : %d%s" % (song.fileName, song.artist, song.size, "MB")

That’s it. Plain simple. WSDL is parsed, complex types get generated on the fly. Something beautiful. It was a little bit more difficult for me to implement something like this in...

Ruby

Using SOAP4R library. Just execute gem install soap4r to get it (really love this tool :) ).
First let’s start with the code:

Ruby
require 'soap/rpc/driver'
require 'soap/wsdlDriver'

class Client
  def initialize
    factory = SOAP::WSDLDriverFactory.new("http://localhost:8080/WSServer/Music?wsdl")
    @driver = factory.create_rpc_driver
  end

  def get_songs(artist)
    songs = @driver.listSongs(:artist => artist)
    return songs
  end

  def get_artists
    artists = @driver.listArtists(nil)
    return artists
  end
end

def print_songs(songs)
  if songs

  end
end

client = Client.new
artists = client.get_artists
artists["return"].each{|artist|
  puts artist
  songs = client.get_songs(artist)["return"];
  songs.each {|song| puts "\t%s : %s : %d%s" % [song.fileName, song.artist, song.size, "MB"]}
}

It does exactly the same. Calls web service for list of artists and then, for each artist, calls for mp3 files. Then just prints everything out to console. It took me quite a while to get it working. First of all – it’s quite hard to find any documentation. Second – SOAP4R does not work with ruby 1.9 without a little hacking: http://railsforum.com/viewtopic.php?id=41231. Next – when you don’t use WSDL to create driver object, results are little bit better, but then you have to know what services you exactly have and want to execute. It’s not a problem in this simple example, but if you need to make it little bit more generic… you’re in trouble. What do I mean by “little bit better”? First, the code:

Ruby
@driver = SOAP::RPC::Driver.new("http://localhost:8080/WSServer/Music",
                                "http://ws.jdevel.wordpress.com/");
@driver.add_method(ARTISTS_METHOD)
@driver.add_method(SONGS_METHOD, "artist")

This way, I’m responsible for declaring endpoint and namespace for service I want to use. I also need to declare all operations I’m going to use, also with parameters (“author”). What’s the difference? When I don’t use WSDL, SOAP4R library gives much better return types from invoking services. I can simply omit ["return"] and get something like using Python. What I do need to know in Ruby is how does each complex type look like thus makes my implementation more sensitive for web service change. How to know what key you should use to get data you have in complex type? Check WSDL and look for operation you want to call:

XML
<operation name="listArtists">
    <input wsam:Action="http://ws.jdevel.wordpress.com/Music/listArtistsRequest" 
     message="tns:listArtists"/>
    <output wsam:Action="http://ws.jdevel.wordpress.com/Music/listArtistsResponse" 
     message="tns:listArtistsResponse"/>
</operation>

Next, find output complex type in xsd (http://localhost:8080/WSServer/Music?xsd=1):

XML
<xs:complexType name="listArtistsResponse">
    <xs:sequence>
        <xs:element name="return" type="xs:string" 
        nillable="true" minOccurs="0" maxOccurs="unbounded"/>
    </xs:sequence>
</xs:complexType>

What you need is value of name attribute.

Anyway, both implementations look really nice and, what's more important, work fine. Both Ruby and Python have nice web services libraries that handle complex types and WSDL parsing.

Filed under: CodeProject, J2EE, Java, Python, Ruby
Tagged: Java, Python, Ruby, Tomcat, WebServices

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)