Přejít k navigační liště

Zdroják » Různé » Java na webovém serveru: implementujeme Jabber

Java na webovém serveru: implementujeme Jabber

Články Různé

Dnes si povíme, jak vytvořit pro naši aplikaci webový chat. A nebude to chat ledajaký, použijeme oblíbený protokol XMPP (Jabber) a napojíme se na existující server. Díky tomu si spolu budou moci povídat jak náhodní kolemjdoucí, kteří přišli na web, tak i uživatelé klasických IM klientů.

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).

Komentáře

Subscribe
Upozornit na
guest
4 Komentářů
Nejstarší
Nejnovější Most Voted
Inline Feedbacks
View all comments
mmmmario

Zdravím, výjimky bych vyhazoval trochu jinak např když místnost neexistuje: NekurakNoSuchRo­om(„Místnost z názvem ‚“+mistnost+„‘ neexistuje.“); apod. Tedy trochu konkrétněji pojmenovat třídy výjimek a v textu zprávy výjimky napsat i „čeho“ se týká jak je v Javě zvykem. Ne tedy obecně ArrayOufOfBounds, ale taky připojit informaci pro snadné opravení chyby jak to dělá Java: index:8, size:5.

amos

Smack kniznica je dobra tak nanajvys do desktopovych klientov pretoze zataz a poolovanie spojeni absolutne nezvlada, rovnako pokia sa zacne prihlasovat viacero klientov tak ukoncuje spojenia ako na beziacom pase a lietaju tam EOException : no more data avalaible – expected end tag. S tou kniznicou by som na chatovom servri moc neexperimentoval. Nam nezostavalo nic ine len si napisat vlastny adapter na XMPP…

amos

pouziva to podobny princip ako JMS ale implementaciaje vlastna. Proti JMS rozhodlo niekolko veci, ktore boli specificke pre ten projekt.

Enum a statická analýza kódu

Mám jednu univerzální radu pro začínající programátorty. V učení sice neexistují rychlé zkratky, ovšem tuhle radu můžete snadno začít používat a zrychlit tak tempo učení. Tou tajemnou ingrediencí je statická analýza kódu. Ukážeme si to na příkladu enum.