Pro komunikaci XMPP protokolem použijeme knihovnu Smack, která nám přináší javovské API a odstíní nás od nízkoúrovňové komunikace s Jabber serverem.
Jako obvykle si nejprve si aktualizujeme zdrojové kódy aplikace pomocí Mercurialu:
$ hg pull $ hg up "16. díl"
Případně je můžete stáhnout jako bzip2 archiv přes web.
Základní práce se Smack knihovnou
Smack je klientská XMPP knihovna od autorů Jabber serveru Openfire. Pomocí následujícího kódu se připojíme k serveru a autentizujeme:
ConnectionConfiguration nastaveni = new ConnectionConfiguration("doména-server"); spojeni = new XMPPConnection(nastaveni); spojeni.connect(); spojeni.login("jméno", "heslo", "zdroj");
Připojení a autentizace jsou oddělené do dvou kroků, protože některé servery umožňují anonymní přístup a některé operace lze provádět už před přihlášením (např. založení nového účtu).
Práce s knihovnou je poměrně jednoduchá a intuitivní. Pomocí následujícího kódu vstoupíme do místnosti a odešleme do ní zprávu:
MultiUserChat muc = new MultiUserChat(spojeni, "název_místnosti"); muc.sendMessage("ahoj");
Příjem zpráv je realizován pomocí posluchačů (listener), které zaregistrujeme, a kteří pak zpracovávají události. Jedná se o stejný princip, jakým se obsluhují události např. ve Swingu (GUI). Posluchače zaregistrujeme pomocí volání metody muc.addMessageListener()
a musí implementovat metodu processPacket()
z rozhraní PacketListener
:
public void processPacket(Packet packet) { if (packet instanceof Message) { Message m = (Message) packet; String od = StringUtils.parseResource(m.getFrom()); String text = m.getBody(); /** uděláme něco se zprávou… */ } }
EJB komponenta
Možná teď přemýšlíte, jak skloubit dohromady navazování spojení s chatovacím serverem a bezestavový HTTP protokol, který používáme na webu. Budeme se přihlašovat k Jabber serveru s každým HTTP požadavkem a po jeho vyřízení spojení zahazovat? Ne, tohle naštěstí není potřeba, Java nabízí řešení v podobě EJB komponent, které „žijí“ na serveru po celou dobu běhu aplikace a mohou tak držet jedno trvalé XMPP spojení. V rámci jednotlivých HTTP požadavků se pak k této komponentě připojíme a využijeme jejích služeb. Toto téma jsme už nakousli v díle srovnávajícím PHP a Javu. Dnes se dostaneme k praktické ukázce.
Základem naší komponenty je třída cz.frantovo.nekurak.ejb.ChatEJB
@Singleton @Startup public class ChatEJB implements ChatRemote { private static final Logger log = Logger.getLogger(ChatRemote.class.getSimpleName()); private Nastaveni nastaveni; private Collection<Spojeni> spojeni = new ArrayList<Spojeni>(); @Override public void posliZpravu(String mistnost, String prezdivka, String zprava) throws NekurakVyjimka { MistnostPripojena mp = najdiMistnost(mistnost); if (mp == null) { throw new NekurakVyjimka("Místnost s tímto názvem neexistuje", null); } else { try { mp.posliZpravu(new ZpravaChatu(prezdivka, zprava)); } catch (Exception e) { log.log(Level.SEVERE, "Selhalo odesílání zprávy", e); throw new NekurakVyjimka("Zprávu se nepodařilo odeslat.", e); } } } /** * @param mistnost název místnosti včetně zavináče a serveru * @param poradoveCislo pořadové číslo poslední zprávy, kterou jsme dostali * @return všechny novější zprávy než dané pořadové číslo * @throws NekurakVyjimka */ @Override public Collection<ZpravaChatu> getZpravy(String mistnost, int poradoveCislo) throws NekurakVyjimka { MistnostPripojena mp = najdiMistnost(mistnost); if (mp == null) { throw new NekurakVyjimka("Místnost s tímto názvem neexistuje", null); } else { return mp.getZpravy(poradoveCislo); } } public ChatEJB() throws NekurakVyjimka { /** TODO: vyřešit lépe. */ nastaveni = new SpravceNastaveni().getNastaveni(); } @PreDestroy public void odpoj() { for (Spojeni s : spojeni) { s.odpoj(); } } @PostConstruct public void inicializuj() throws NekurakVyjimka, NamingException { pripojXMPP(); } private void pripojXMPP() throws NekurakVyjimka { try { for (UcetRobota u : nastaveni.getUctyRobota()) { Spojeni s = new Spojeni(u); spojeni.add(s); } } catch (Exception e) { throw new NekurakVyjimka("Chyba při připojování.", e); } } /** * @param nazev Název místnosti, kterou hledáme. * @return nalezená místnost, nebo null, pokud místnost nebyla nalezena. */ private MistnostPripojena najdiMistnost(String nazev) { for (Spojeni s : spojeni) { for (MistnostPripojena mp : s.getMistnosti()) { if (mp.porovnejNazev(nazev)) { return mp; } } } return null; }
Důležité jsou zde použité anotace. @Singleton
říká, že EJB komponenta bude v systému jen jedna, což v našem případě znamená, že z našeho serveru povede jen jedno XMPP spojení na Jabber server, bez ohledu na to, kolik klientů náš server bude obsluhovat. Pomocí anotace @Startup
říkáme, že se komponenta má vytvořit hned po nasazení aplikace (deploy). Jinak by se totiž vytvořila až ve chvíli, kdy by ji poprvé někdo potřeboval. Anotací @PreDestroy
pak označíme metodu, která se postará o korektní ukončení navázaného XMPP spojení – zavolá se např. při vypínání aplikačního serveru nebo deaktivaci aplikace.
Na straně klienta
Nad EJB vrstvou máme webové rozhraní (viz chat.jsp
), které zpřístupňuje funkcionalitu komponenty webovému prohlížeči. V něm pomocí AJAXu odesíláme zprávy do chatovací místnosti a periodicky kontrolujeme zda přišly nějaké nové zprávy. Tento kód naleznete v souboru chat.js
.
Závěr
V dnešním díle jsme se naučili pracovat s XMPP protokolem v Javě, což se nám může hodit i jinde než na webu – např. při tvorbě IM klienta nebo XMPP služby. Webovou část našeho chatu bychom později mohli přepsat tak, aby nevyžadovala periodickou kontrolu nových zpráv, např. s použitím moderní technologie Webových socketů. K tomu je ale potřeba podstatnější zásah do naší komponenty – je třeba ji upravit, aby posílala nové zprávy všem přihlášeným klientům a ne jen pasivně čekala, až se jí někdo zeptá, jaké jsou nové zprávy.
Odkazy
- XMPP – Extensible Messaging and Presence Protocol.
- Smack – Knihovna pro práci s XMPP (Jabberem).
- Smack – dokumentace ke knihovně.
- New Features in EJB 3.1 – Novinky v EJB 3.1 (např. Singletony).
Přehled komentářů