Затем попытался использовать их, используя 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" ?> < 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): 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 @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 = 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" ) |
Таким образом, я отвечаю за объявление конечной точки и пространства имен для службы, которую я хочу использовать. Мне также нужно объявить все операции, которые я собираюсь использовать, также с параметрами («автор»). Какая разница? Когда я не использую 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 в блоге «Истории из мира разработки» .