30 kwietnia 2009

Z rozdziału 15. o usługach sieciowych z DGG2

Przyszła pora na relację z rozdziału 15. Web Services z "The Definitive Guide to Grails, Second Edition".

Temat usług sieciowych zazwyczaj rozpoczyna się od protokołu SOAP (ang. Simple Object Access Protocol), który "Simple" był w założeniu i okazał się być całkowicie inny w praktyce. Na scenie pojawił się REST (ang. Representational State Transfer), który jest prostym (nie z nazwy, ale faktycznie) podejściem architektonicznym, który opiera się na wykorzystaniu HTTP do komunikacji między usługami sieciowymi (do tej pory mówiąc o Web Services na myśl przychodził SOAP, który z narodzinami REST schodzi w cień). Zaletą REST jest wykorzystanie sprawdzonego protokołu HTTP do przesyłania komunikatów w dowolnym formacie, np. JSON (ang. JavaScript Object Notation), POX (ang. Plain Old XML) czy wręcz SOAP. Istotną zaletą REST jest powiązanie poleceń HTTP na odpowiednie polecenia w aplikacji CRUD, tj. poleceniom PUT, GET, POST i DELETE odpowiadają, odpowiednio, akcje Create, Read, Update i Delete wykonywane na pewnym zasobie w aplikacji. Komunikacja za pomocą formatu JSON znacznie upraszcza tworzenie aplikacji AJAX, ponieważ JavaScript ma natywne wsparcie dla JSON (nie przypadkiem tak się nazywa).

Przekładając teorię REST na możliwości Grails - polecenia HTTP to akcje kontrolera, który operuje na zasobach - klasach dziedzinowych. Najbardziej trywialną obsługą REST w Grails jest oparcie się na parametrze request.method, np. (Listing 15-1):
 class AlbumController {
def index = {
switch(request.method) {
case "GET":
return show()
case "PUT":
return save()
...
}
}
}
Jest jednak prostszy sposób (Listing 15-2):
 static mappings = {
"/album/$id?"(controller:"album") {
action = [GET:'show', PUT:'save', POST:'update', DELETE:'delete']
}
}
Przy bardziej skompilowanym adresie URL, np. "/music/$artist/$album?/$song?", wymagamy, aby kontroler, za pomocą serii if-else, wykonał odpowiednią akcję. Możemy uprościć kontroler przenosząc decyzję "co i kiedy" na konfigurację Grails i bardziej specjalizowane kontrolery, np. (Listing 15-4):
 "/music/$artistName/$albumTitle?/$songTitle?" {
controller = {
if (params.albumTitle && params.songTitle) return 'song'
else if (params.albumTitle) return 'album'
else return 'artist'
}
action = [GET:'show', PUT:'save', POST:'update', DELETE:'delete']
}
Grails posiada mechanizm negocjacji (zwracanej) treści (ang. content negotiation) na podstawie nagłówków HTTP: ACCEPT lub CONTENT_TYPE, parametru format oraz rozszerzenia w adresie URL.

Definicja akceptowanych typów MIME jest w grails-app/conf/Config.groovy przez parametr grails.mime.types.

Korzystając z metody withFormat w kontrolerze możemy obsługiwać różne typy MIME, np. (Listing 15-6):
 import grails.converters.*
class ArtistController {
def show = {
def artist = ...
if (artist) {
withFormat {
html artist:artist, albums:artist?.albums
xml { render artist as XML }
}
} else {
response.sendError 404
}
}
}
Konstrukcja "as XML" korzysta z zaimportowanej klasy-konwertera grails.converters.XML, aby automatycznie wysłać odpowiedź w postaci XML.

Do odpowiedzi w Grails (a właściwie Groovy) korzystamy z XmlSlurper oraz dostępu za pomocą notacji OGNL, np. (Listing 15-8):
 url = new URL("...")
conn = url.openConnection()
conn.addRequestProperty("accept", "application/xml")

artist = new XmlSlurper().parse(conn.content)

println "Artist Name = ${artist.name}"
Jeśli przeglądarka wyśle nagłówek ACCEPT z wartością */*, wtedy Grails bierze pierwszą metodę w domknięciu withFormat. Możemy również wskazać Grails, aby brał inną poprzez grails.mime.types (domyślnie jest all: '*/*', co oznacza, że w withFormat szukana jest metoda all).

Najprostszym sposobem jest wybór typu żądania/odpowiedzi po rozszerzeniu w URL. Domyślnie, Grails obsługuje żądania z URL zakończonym na xml jako żądania XML i w odpowiedzi wysyła XML. Wyłączamy to zachowanie przez zmienną grails.mime.type.extensions=false w grails-app/conf/Config.groovy.

Ostatnim rozwiązaniem jest za pomocą parametru format. Łącząc to z mapowaniem URL możemy wskazywać rodzaj żądania, np. (Listing 15-14):
 "/music/$artist(controller:"artist") {
action = "show"
format = "xml"
}
Wskazanie strony GSP obsługującej dany typ żadania, to dodanie typu żądania do nazwy pliku przed rozszerzeniem gsp, np. show.wml.gsp. W przypadku braku, brana jest domyślna strona GSP, np. show.gsp.

Do wysłania odpowiedzi w postaci JSON korzystamy z konwertera grails.converters.JSON i wywołania "as JSON", np. (Listing 15-20):
 import grails.converters.*

...
withFormat {
json { render artist as JSON }
}
W książce autorzy prezentują użycie wtyczki Firefox Poster do śledzenia ruchu między przeglądarką a serwerem HTTP.

Domyślnie Grails obsługuje żądania XML i JSON, jak żądania HTML.

Standardy RSS i Atom służą do publikowania strumieni informacji w Sieci. Możliwość prezentowania danych w aplikacji GTunes autorzy opierają na wtyczce feeds. Pamiętam, jak w komentarzu do wpisu o Grails ktoś wspomniał o konstrukcji g.message(code:"gtunes.latest.feed.title") do pobrania komunikatu dla danego języka. Właśnie teraz pojawiło się wykorzystanie - w kodzie kontrolera do stworzenia strumienia. Z wykorzystaniem znacznika <feed:meta> konfigurujemy link do RSS lub Atom, np. (Listing 15-31):
 <html>
<head>
<feed:meta kind="rss"
version="2.0"
controller="store"
action="latest"
params="[format='rss']" />
...
</head>
...
</html>
Korzystanie z dobrodziejstw SOAP możliwe jest z pomocą wtyczek xfire, axis2 oraz metro. Autorzy prezentują użycie wtyczki xfire. Wystawienie kontrolera jako usługi sieciowej z SOAP to static expose = ['xfire'] (zamiast xfire, moglibyśmy użyć również rmi lub innej technologii).

Na koniec opisuje się pokrótce projekt Groovy-WS.