Статьи

Веб-сервисы на Ruby, Python и Java

Сегодня мне пришлось подготовить несколько примеров, чтобы показать, что веб-сервисы совместимы. Поэтому я создал простой веб-сервис на Java с использованием Metro и запустил его на Tomcat.

Затем попытался использовать их, используя Python и Ruby. Вот как все это закончилось …

Веб-сервис на Java

Я начал с простого веб-сервиса на Java:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
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");
            }
        }
    }
}

Нужен также простой bean-компонент для получения более сложных типов:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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;
    }
}

Он просто перечисляет все подкаталоги в жестко закодированном каталоге FOLDER и рассматривает его как список исполнителей в музыкальной коллекции. Затем вы можете выполнить метод listSongs и получить список mp3-файлов в подпапке Artist.

Чтобы сделать его веб-службой, все, что вам нужно сделать, это аннотировать класс с помощью @WebService (serviceName = «Music»), и каждый метод, который вы хотите представить как операцию веб-службы, должен быть помечен @WebMethod (operationName = «listArtists»).

Это должно быть все, если вы развертываете его на GlassFish, но я использовал Tomcat, поэтому потребовалось еще 3 шага:

1. Добавьте баночки Metro 2.0 в WEB-INF / lib
2. Добавьте Metro сервлет и слушатель в web.xml:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
<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>

Вы, вероятно, не должны ничего менять здесь. Просто вставьте его в файл web.xml в узле веб-приложения.

3. Добавьте файл sun-jaxws.xml в WEB-INF с объявлением конечной точки:

1
2
3
4
<?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>
  • реализация должна соответствовать вашему классу @WebService
  • имя должно соответствовать serviceName в аннотации @WebService
  • шаблон URL должен соответствовать шаблону URL, который вы объявили в отображении сервлета

Также не должно быть необходимости редактировать эти XML-файлы, если вы создаете их в NetBeans.

Теперь запустите Tomcat и разверните свое приложение. Вы должны иметь возможность получить доступ к своему сервису через что-то вроде

HTTP: // локальный: 8080 / WSServer / Музыка

и увидеть что-то вроде этого:

WSDL будет доступен через

HTTP: // локальный: 8080 / WSServer / Музыка WSDL

Схема для сложных типов:

HTTP: // локальный: 8080 / WSServer / Музыка XSD = 1?

Если у вас это работает, вы можете начать со следующих клиентов.

Клиент Python
Я начал поискать какую-нибудь симпатичную библиотеку веб-сервисов для python и нашел Suds. Я действительно не использовал ничего подобного. Внедрение WS-клиента заняло у меня около 15 минут. Поддержка сложных типов, конечно, и в прошлый раз, когда я использовал Python для чего-то большего, чем 5 строк, было около 3 лет назад. Вы действительно должны попробовать это.

Итак, вот код:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
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")

Вот и все. Просто, просто. WSDL анализируется, сложные типы генерируются на лету. Что-то красивое. Мне было немного сложнее реализовать что-то подобное в…

Ruby Client
Использование библиотеки SOAP4R. Просто выполнить

гем установить soap4r

чтобы получить его (очень нравится этот инструмент). Сначала давайте начнем с кода:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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"]}
}

Это точно так же. Вызывает веб-сервис для списка исполнителей, а затем, для каждого исполнителя, вызывает mp3-файлы. Затем просто выводит все на консоль.

Мне потребовалось много времени, чтобы заставить его работать. Прежде всего — довольно сложно найти какую-либо документацию. Второе — SOAP4R не работает с ruby ​​1.9 без небольшого взлома:

http://railsforum.com/viewtopic.php?id=41231

Далее — когда вы не используете WSDL для создания объекта драйвера, результаты немного лучше, но тогда вы точно должны знать, какие сервисы у вас есть, и хотите выполнить. В этом простом примере это не проблема, но если вам нужно сделать его немного более общим … у вас будут проблемы.

Что я имею в виду под «немного лучше»? Сначала код:

1
2
3
@driver.add_method(ARTISTS_METHOD)
@driver.add_method(SONGS_METHOD, "artist")

Таким образом, я отвечаю за объявление конечной точки и пространства имен для службы, которую я хочу использовать. Мне также нужно объявить все операции, которые я собираюсь использовать, также с параметрами («автор»). Какая разница? Когда я не использую WSDL, библиотека SOAP4R дает гораздо лучшие типы возвращаемых данных из вызывающих сервисов. Я могу просто опустить [«return»] и получить что-то вроде использования Python.

Что мне действительно нужно знать в Ruby, так это то, как выглядит каждый сложный тип, что делает мою реализацию более чувствительной к изменениям веб-сервиса. Как узнать, какой ключ вы должны использовать для получения данных сложного типа? Проверьте WSDL и найдите операцию, которую хотите вызвать:

1
2
3
4
<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>

Затем найдите выходной комплексный тип в xsd

1
2
3
4
5
<xs:complexType name="listArtistsResponse">
    <xs:sequence>
        <xs:element name="return" type="xs:string" nillable="true" minOccurs="0" maxOccurs="unbounded"/>
    </xs:sequence>
</xs:complexType>

Что вам нужно, это значение атрибута имени. В любом случае, обе реализации выглядят действительно хорошо и, что более важно, работают нормально. И в Ruby, и в Python есть отличные библиотеки веб-сервисов, которые обрабатывают сложные типы и разбирают WSDL.

Ссылка: веб-сервисы на Ruby, Python и Java от нашего партнера по JCG в блоге «Истории из мира разработки» .

Статьи по Теме :