Devel.cz Lupa Měšec Podnikatel Root Zdroják.cz DigiZone Slunečnice Vitalia TopDrive KupDnes Navrcholu NovýTarif Dobrý web Weblogy Woko Jagg Computer.cz SK: MojeLinky

Hlavní navigace

Java na webovém serveru: píšeme REST API

V dnešním díle našeho seriálu se budeme věnovat tvorbě REST API. Jelikož o tomto přístupu jste si zde na Zdrojáku mohli přečíst už dříve, teorii si zopakujeme jen velmi stručně a budeme se věnovat hlavně praktické stránce implementace REST rozhraní v Javě, konkrétně pomocí nástroje Jersey.

Tweetni to Twitter Jaggni to! Jagg Del.icio.us Delicious

Dosud jsme se v naší aplikaci zabývali komunikací s uživatelem – uživatelským rozhraním. Aplikace mohou ale komunikovat i s jinými aplikacemi a k tomu slouží API. Přestože „na druhém konci drátu“ v takovém případě není člověk, ale aplikace, hojně se dnes pro realizaci API využívá web resp. HTTP protokol.

Existují dva základní druhy webových služeb: klasické SOAP služby a RESTful služby.

Zatímco SOAP služby odpovídají principu RPC, tedy volání procedur, REST služby jsou orientované na „zdroje“. Tento rozdíl se projevuje i v URL – v případě SOAP bude obsahovat typicky nějaké sloveso, např. přijmiObjednávku, kdežto v případě REST služby bude URL obsahovat jen podstatné jméno objednávka.

Zda zvolit jeden nebo druhý přístup záleží na konkrétním použití. Jako autoři aplikace musíte vědět, zda spíše voláte „procedury“ nebo spíše přistupujete ke „zdrojům“ a provádíte nad nimi CRUD operace. Toto rozhodnutí by mělo vycházet z logické podstaty vaší aplikace. V dnešním díle se budeme zabývat RESTful webovými službami – ovšem o SOAP WS nebudete ochuzeni – přijde na ně řada v dalších dílech seriálu.

Více o RESTu se můžete dočíst v článku REST: architektura pro webové API.

Specifikace a implementace

Jelikož ve světě Javy se relativně hodně dbá na standardizaci, máme zde jednak specifikaci RESTful webových služeb „JSR-000311 JAX-RS: The Java API for RESTful Web Services“ a jednak několik jejích implementací. V naší aplikaci použijeme referenční implementaci zvanou Jersey. Stejně dobře (nebo i lépe) by nám měla posloužit i jakákoli jiná implementace  – např. CXF od Apache. Jersey je ale obsažena v aplikačním serveru, který používáme, takže její využití bude přímočařejší.

Java REST

Jednoduchý příklad

Nejprve si aktualizujeme zdrojové kódy naší aplikace pomocí Mercurialu:

$ hg pull
$ hg up "11. díl"

Případně je můžete stáhnout jako bzip2 archiv přes web.

Pro přehlednost jsem REST API vyčlenil do zvláštního webového modulu ( nekurak.net-rest), aby se jeho kód nepletl dohromady s kódem pro uživatelské rozhraní. Abychom mohli využívat REST webové služby, přidáme si do web.xml servlet, který bude tyto požadavky obsluhovat.

<servlet>
    <servlet-name>REST</servlet-name>
    <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>REST</servlet-name>
    <url-pattern>/*</url-pattern>
</servlet-mapping>

Servlet si můžeme pojmenovat, jak se nám líbí ( REST). Důležitá je cesta, na kterou je namapován – v našem případě je to přímo kořen webového modulu. Pokud bychom REST API implementovali ve stejném modulu jako zbytek webové aplikace, namapovali bychom servlet např. na /zdroje/*.

Nyní přistoupíme k vlastní implementaci služby. Jedná se o prostou javovou třídu – nemusí implementovat žádné rozhraní ani být potomkem jiné zvláštní třídy – pouze obsahuje anotace z balíčku javax.ws.rs. Nejjednodušší REST služba vypadá takto:

@Path("clanek")
public class ClankyREST {

    private static String data;

    @GET
    @Produces("text/plain")
    public String getText() {
    return "Naposledy sem někdo poslal:\n" + data + "\n";
    }

    @PUT
    @Consumes("text/plain")
    public String putText(String data) {
    ClankyREST.data = data;
    return "Právě jsme od vás přijali:\n" + data + "\n";
    }
}

Pomocí anotace třídy @Path("clanek") určíme cestu, na které bude daný zdroj dostupný. Cesta je relativní vůči servletu definovanému výše (v našem případě kořen webového modulu). V případě lokálního testovacího serveru bude výsledné URL vypadat nějak takhle:  http://localhost:8080/nekurak.net-rest/clanek

Následně přidáme anotace k metodám. Pomocí anotací @GET, @PUT, @POST a @DELETE (případně @HEAD) si označíme metody, které mají obsluhovat požadavky klientů – anotace odpovídají stejnojmenným HTTP metodám.

Anotace @Produces a @Consumes slouží k definování MIME typu odesílaných nebo přijímaných dat. Pro jednoduchost jsme zvolili prostý text.

Funkčnost čtení ( GET) si ověříme jednoduše nasměrováním webového prohlížeče na příslušnou URL. Pro vyzkoušení odesílání dat na server ( PUT) můžeme použít nástroj cURL. V Debianu nebo Ubuntu si ho snadno nainstalujete pomocí příkazu:

aptitude install curl

Data naší REST službě odešleme z příkazové řádky takto:

curl -i -X PUT -H "Content-Type: text/plain" --data "Ahoj. Jak se máš?" http://localhost:8080/nekurak.net-rest/clanek

Služba data přijme a uloží si je do statické proměnné.Data si následně můžeme „vyzvednout“ na stejné URL – tentokrát ale použijeme metodu GET (k čemuž nám stačí webový prohlížeč).

Výše uvedená služba moc užitečná není a slouží pouze k osvojení základů. Nyní tedy přistoupíme k reálnějšímu příkladu.

Praktická ukázka

Naším cílem bude vytvořit REST API pro aplikaci Nekuřák.net, resp. její „redakční“ část – napíšeme si API, které nám umožní pracovat s „články“  – ty jsou prozatím velmi jednoduché: jen struktura, která má nadpis a text.

Toto API můžeme použít např. pro komunikaci s editorem, který poběží na našem desktopu a ve kterém budeme moci pohodlně psát články.

Požadované funkce jsou:

  • vypsat si seznam článků v systému
  • získat konkrétní článek
  • vložit nový článek
  • aktualizovat článek
  • smazat článek

Pro tyto úkoly nám už prostý text ( text/plain) stačit nebude, a tak použijeme univerzální formát XML. K tomu budeme potřebovat mapování mezi světem objektů a světem XML – to si naštěstí nemusíme psát ručně a stačí použít JAXB (Java Architecture for XML Binding), díky které to zvládneme celkem bezbolestně pomocí pár anotací.

Vsuvka: mapování mezi objekty a XML

Vytvoříme si jednoduchou obalovou třídu ClanekXML, která bude sloužit pro mapování objektů na XML a XML na objekty. Použijeme anotaci třídy @XmlRootElement(name="clanek"), která říká, že třída se bude mapovat na XML element s názvem clanek, který může být kořenovým XML elementem. Dále přidáme anotaci @XmlElement ke metodám reprezentujícím data (nadpis, id a text).

A to je vše – při převodu instance třídy ClanekXML dojde v vygenerování XML, které vypadá např. takto:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<clanek>
    <id>1</id>
    <nadpis>Úvod</nadpis>
    <text>Text úvodního článku</text>
</clanek>

A zároveň můžeme díky těmto anotacím převádět zpět XML na objekty. Mapování je obousměrné.

XML anotace bychom případně mohli použít rovnou ve třídě cz.frantovo.nekurak.dto.Clanek a obalovou třídu ClanekXML vůbec nevytvářet, ale jelikož konverzi z a do XML potřebujeme jen v REST API a v jiných vrstvách nás XML mapování nezajímá, zvolil jsem přístup s obalovou třídou.

Implementace REST API

Oproti předchozímu jednoduchému příkladu už neukládáme data do statické proměnné, ale voláme metody z EJB vrstvy, která následně volá metody datové vrstvy.

@Path("clanek")
public class ClankyREST {

    private static final String MIME_XML = "text/xml";
    private static final String MIME_TEXT = "text/plain";
    private HledacSluzby hledac = new HledacSluzby();

    /** Vypíšeme seznam všech článků v systému */
    @GET
    @Path("/")
    @Produces(MIME_XML)
    public Collection<ClanekXML> seznam() {
    Collection<ClanekXML> vysledek = new ArrayList<ClanekXML>();
    Collection<Clanek> clanky = hledac.getClanekEJB().getClanky();

    for (Clanek c : clanky) {
        vysledek.add(new ClanekXML(c));
    }

    return vysledek;
    }

    /** Získáme konkrétní článek */
    @GET
    @Path("/{id}")
    @Produces(MIME_XML)
    public ClanekXML ziskej(@PathParam("id") int id) {
    Clanek c = hledac.getClanekEJB().getClanek(id);
    return new ClanekXML(c);
    }

    /**
     * Vložíme nový článek
     * @return ID založeného článku (návratový typ musí být String – ne int)
     */
    @POST
    @Consumes(MIME_XML)
    @Produces(MIME_TEXT)
    public String zaloz(ClanekXML xml) {
    int id = hledac.getClanekEJB().zalozClanek(xml.getClanek());
    return id + "\n";
    }

    /** Aktualizujeme článek */
    @PUT
    @Consumes(MIME_XML)
    @Path("/{id}")
    public void uprav(@PathParam("id") int id, ClanekXML xml) {
    xml.setId(id);
    hledac.getClanekEJB().upravClanek(xml.getClanek());
    }

    /** Smažeme článek */
    @DELETE
    @Path("/{id}")
    public void smaz(@PathParam("id") int id) {
    Clanek c = new Clanek();
    c.setId(id);
    hledac.getClanekEJB().smazClanek(c);
    }
}

Metoda seznam() slouží k vypsání všech článků. Uživatel pošle HTTP GET požadavek na danou službu např. http://localhost:8080/nekurak.net-rest/clanek a obdrží výpis ve formátu XML.

Metoda ziskej() slouží k získání jednoho článku. Opět používáme HTTP GET, ale jelikož už přistupujeme ke konkrétnímu zdroji, URL obsahuje číslo článku – např. http://localhost:8080/nekurak.net-rest/clanek/1.

URL požadavku nemusíme nijak ručně parsovat – pouze zadáme jako cestu vhodný vzor: @Path("/{id}") a následně si necháme číslo článku vložit do příslušného parametru metody: @PathParam("id") int id. Implementace JAX-RS se už postará o kontrolu parametrů a k nám už se dostane bezpečná proměnná typu int. Pokud by např. uživatel požadoval URL /nekurak.net-rest/clanek/xyz, dostal by odpověď 404 Nenalezeno, jelikož na dané adrese není mapován žádný zdroj.

Třetí metoda slouží k vytváření nových článků. Používáme HTTP metodu POST a URL číslo článku neobsahuje – to se totiž vygeneruje automaticky (v datové vrstvě) a klient ho dostane zpět v těle HTTP odpovědi. Operaci si můžeme vyzkoušet z příkazové řádky pomocí nástroje cURL:

curl -i -X POST -H "Content-Type: text/xml" --data "<clanek><nadpis>Ahoj</nadpis><text>vloženo CURLem</text></clanek>" -u franta http://localhost:8080/nekurak.net-rest/clanek

Metoda uprav() slouží k aktualizaci existujícího článku. Který článek se bude aktualizovat závisí na URL. Odeslání dat je obdobné, pouze tentokrát použijeme HTTP PUT operaci.

Jelikož zapisovat složitější dokumenty do příkazové řádky je nepřehledné, můžeme si je připravit jako soubor na disku a odeslat následujícím způsobem:

cat připravený-soubor.xml | curl -i -X PUT -H "Content-Type: text/xml" --data @- -u franta http://localhost:8080/nekurak.net-rest/clanek/1

Všimněte si, že v REST metodách, kde přijímáme vstup od uživatele se nemusíme nijak starat o konverzi XML na objekty – pouze deklarujeme jako parametr metody daný datový typ ( ClanekXML) a automaticky dostáváme jeho instanci.

Poslední metoda slouží k mazání článků. Nepracuje s žádnými vstupními ani výstupními daty (nejsou potřeba). Jediná informace se předává v URL – číslo článku, který chceme smazat.

curl -i -X DELETE -u franta http://localhost:8080/nekurak.net-rest/clanek/1

Zabezpečení

Jelikož REST API umožňuje provádět i zápis a mazání, povolíme přístup jen oprávněným uživatelům. Využijeme toho, co jsme se naučili v jednom z předešlých dílů: Autorizace a autentizace, a zabezpečíme metody EJB (třída ClanekEJB) pomocí anotace @RolesAllowed("redaktor"). Povolíme tak přístup jen vybraným uživatelům s rolí redaktor.

Nezapomeneme na úpravu web.xml, kde nastavíme způsob přihlašování – pro REST API zvolíme HTTP BASIC autentizaci (zatímco pro uživatelské rozhraní jsme používali HTML formuláře).

<security-role>
    <role-name>redaktor</role-name>
</security-role>
<security-constraint>
    <web-resource-collection>
    <web-resource-name>Nekuřák.net REST API</web-resource-name>
    <url-pattern>/*</url-pattern>
    <http-method>PUT</http-method>
    <http-method>POST</http-method>
    <http-method>DELETE</http-method>
    </web-resource-collection>
    <auth-constraint>
    <role-name>redaktor</role-name>
    </auth-constraint>
</security-constraint>
<login-config>
    <auth-method>BASIC</auth-method>
    <realm-name>nekurakNET</realm-name>
</login-config>

Pokud bychom úpravu web.xml neprovedli, uživatel by neměl možnost se přihlásit a pokusy o vytváření nebo mazání by končily neúspěchem ( AccessException: CORBA NO_PERMISSION). A pokud bychom naopak vynechali anotace uvnitř EJB tříd, bylo by zabezpečení naší aplikace velice křehké – záviselo by pouze na nastavení prezentační webové vrstvy. Potřebné jsou tedy obě úpravy.

Závěr

Dnes jsme se naučili základy tvorby REST API v Javě pomocí JAX-RS. Jedná se o poměrně rozsáhlou problematiku, nicméně po přečtení dnešního článku byste měli být schopní naprogramovat API, které poslouží např. pro komunikaci desktopových klientů s danou webovou aplikací. REST API můžete rovněž využít při tvorbě moderních AJAXových aplikací – implementace serverové části takové aplikace pomocí JAX-RS může být vhodnější než implementace pomocí JSP nebo klasických servletů.

Odkazy

František Kučera

Franta Kučera působí jako Java vývojář na volné noze. Programování je jeho koníčkem už od dětství. Kromě toho má rád Linux, relační SŘBD a XML.

Školení Google Analytics pro pokročilé

DW - Školení Google Analytics
  • Jak využít nové funkce Google Analytics
  • Vyhodnocování kampaní díky používání Multichannel funnels
  • Kde návštěvníci vašeho webu utíkají z objednávacího procesu.
  • Nebudete opakovat časté chyby při vyhodnocování dat o návštěvnosti.

Detailní informace o školení Google Analytics pro pokročilé »

Přehled názorů

Odkaz
Franta Kučera 7. 4. 2010 00:25
Nový
└ 
Re: Odkaz
peter 7. 4. 2010 13:16
Nový
SOAP vs REST
peter 7. 4. 2010 11:47
Nový
└ 
Re: SOAP vs REST
Franta Kučera 7. 4. 2010 19:06
Nový
 
└ 
Re: SOAP vs REST
peter 7. 4. 2010 19:56
Nový
 
 
└ 
Re: SOAP vs REST
Franta Kučera 8. 4. 2010 01:31
Nový
 
 
 
└ 
Re: SOAP vs REST
peter 8. 4. 2010 14:42
Nový
 
 
 
 
└ 
Re: SOAP vs REST
marmax 26. 4. 2010 12:24
Nový
 
 
 
 
 
├ 
Re: SOAP vs REST
marmax 26. 4. 2010 12:28
Nový
 
 
 
 
 
└ 
Re: SOAP vs REST
peter 26. 4. 2010 20:52
Nový
       

Tento text je již více než dva měsíce starý. Chcete-li na něj reagovat v diskusi, pravděpodobně vám již nikdo neodpoví. Pro řešení aktuálních problémů doporučujeme využít naše diskusní fórum.

Zasílat nově přidané příspěvky e-mailem