Komunikace mezi Servlety

Tato část se zabývá komunikací mezi servlety. Servlety na webovém serveru spolu mohou komunikovat a sdílet mezi sebou zdroje jako jsou třeba proměnné.

Request Dispatcher

Komunikace mezi servlety je možná díky RequestDispatcher rozraní. Používá se k delegování requestů ze servletu do jiných zdrojů (např. servlet, HTML soubor, JSP soubor...). Servlet může pro request provést nějakou akci a poslat jej dál na jiný servlet pro další zpracování.

RequestDispatcher můžeme získat z HttpServletRequest objektu pomocí metody getRequestDispatcher a použít jej. Rozhraní RequestDispatcher definuje dvě metody, které můžeme pro komunikaci mezi servlety použít. Následující tabulka je popisuje.

MetodaNávratový typPopis
forward(ServletRequest request, ServletResponse response)voidPředá request ze servletu do jiného zdroje (např. servletu, JSP stránky...).
include(ServletRequest request, ServletResponse response)voidPřidá obsah jiného zdroje (např. servletu, JSP stránky...) do response.

Rozdíl mezi metodou forward a include je ten, že když používáme metodu forward, tak v serveletu pro request nejdříve provedeme nějakou akci a poté request předáme pro zpracování do jiného servletu. Metodu include můžeme použít k tomu, že můžeme request předat ke zpracování do jiného servletu, který nám může třeba něco nastavit do response, a poté pokračovat ve zpracování requestu.

Servlet Context

Kromě toho, že můžeme RequestDispatcher objekt získat z HttpServletRequest objektu, tak jej můžeme získat i ze ServletContextu. Servlet context je v podstatě adresář, ve kterém jsou na serveru servlety umístěny. Servlety, které máme v jednom projektu vlastně patří do stejného servlet contextu. Můžeme mít na serveru více aplikací a tedy i více servlet contextů.

Při spuštění aplikace vytváří web container objekt typu ServletContext, ke kterému můžeme v servletu získat referenci pomocí metody getServletContext. ServletContext objekt definuje metody, které může servlet použít ke komunikaci se servlet containerem. Můžeme jej například použít k získání konfiguračních informací z web.xml. Pro jednu webovou aplikaci existuje jen jeden ServletContext objekt.

Context parametry

Tato část je sice o komunikaci mezi servlety, ale myslím si, že se tu hodí také zmínit, jakým způsobem můžeme mezi servlety sdílet konfigurační parametry z web.xml. Slouží k tomu element jménem context-param. Pomocí jeho podelementu param-name definujeme název parametru, a pomocí podelementu param-value definujeme hodnotu parametru.

Pro ukázku si založíme nový Maven projekt, ve kterém si soubor web.xml vytvoříme a nadefinujeme context parametr. Následující ukázka ukazuje, jak to můžeme udělat.

  • src
    • main
      • java
      • resources
      • webapp
        • WEB-INF
    • test
      • java
      • resources
  • target
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>io.github.jirkasa</groupId>
    <artifactId>context-parametry</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>
    <name>context-parametry</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>17</java.version>
        <maven.compiler.source>${java.version}</maven.compiler.source>
        <maven.compiler.target>${java.version}</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
</project>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://Java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" 
id="WebApp_ID" version="3.0">
    <context-param>
        <param-name>text</param-name>
        <param-value>Toto je můj text.</param-value>
    </context-param>
</web-app>

Teď si můžeme vytvořit servlet a parametr v něm použít. Jelikož se jedná o context parametr, tak jej budeme moci použít v jakémkoliv servletu. Nejedná se o inicializační parametr pro servlet, o kterém jste se mohli dozvědět v jedné z předešlích částí. Náš servlet můžeme pojmenovat třeba jako MujServlet. Implementujeme mu metodu doGet, ve které získáme pomocí metody getServletContext servlet context a poté z něj získáme parametr, který použijeme na stránce, kterou vygenerujeme. Následující ukázka servlet ukazuje.

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class MujServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
        // získání servlet contextu
        ServletContext context = getServletContext();
        // získání konfiguračního parametru ze servlet contextu
        String text = context.getInitParameter("text");
        
        res.setContentType("text/html; charset=utf-8");
        try (PrintWriter out = res.getWriter()) {
            out.println("<!DOCTYPE html>");
            out.println("<html>");
            out.println("<head>");
            out.println("<meta charset=\"UTF-8\">");
            out.println("<title>Context parametry</title>");
            out.println("</head>");
            out.println("<body>");
            out.println("<h1>Context parametr</h1>");
            out.println("<p>" + text + "</p>");
            out.println("</body>");
            out.println("</html>");
        }
    }
}

Ve web.xml můžeme servlet namapovat třeba na "/", což zapříčiní, že se bude volat vždycky.

  • src/main/webapp/WEB-INF
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://Java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" 
id="WebApp_ID" version="3.0">
    <context-param>
        <param-name>text</param-name>
        <param-value>Toto je můj text.</param-value>
    </context-param>
    <servlet>
        <servlet-name>MujServlet</servlet-name>
        <servlet-class>MujServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>MujServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

Pokud si aplikaci spustíte a navštívíte http://localhost:8080/context-parametry/, tak uvidíte na stránce vypsaný text, který jste v souboru web.xml nastavili jako parametr.

Stránka s textem podle context parametru

Rozdíl mezi request dispatcherem ze servlet requestu a servlet contextu

Rozhraní ServletContext také definuje metodu getRequestDispatcher, stejně jako ServletRequest, kterou můžeme použít k získání objektu typu RequestDispatcher. Jsou v tom nějaké rozdíly. RequestDispatcher získaný ze ServletRequestu například nemůžeme použít mimo aktuální servlet context, ale RequestDispatcher získaný ze ServletContextu můžeme. Jaký je v tom rozdíl si můžete přečíst třeba na této stránce. Přiznám se že tomu moc nerozumím.

Metoda forward

Jak jste se již mohli dočíst, tak rozhraní RequestDispatcher definuje metodu forward a include. Již jsem tak nějak popsal k čemu slouží. Pomocí metody forward můžeme request předat ke zpracování na jiný servlet. Teď si zkusíme pro ukázku vytvořit projekt, ve kterém metodu forward použijeme.

V našem projektu budeme mít dva servlety. První vygeneruje náhodné číslo a druhý toto číslo zobrazí. Začneme tím, že si vytvoříme ten první. Můžeme jej pojmenovat třeba jako NahodneCisloServlet. Věci, které v tomto tutoriálu děláme, jsou samozřejmě blbosti. V praxi byste něco takového neprogramovali. Nám tu jde jen o to, abychom si na jednoduchých příkladech vyzkoušeli, jak věci fungují. Následující ukázka servlet ukazuje. Na začátku se vygeneruje náhodné číslo, které se potom nastaví jako atribut requestu metodou setAttribute aby se k němu druhý servlet dostal. Také se metodou getRequestDispatcher získává RequestDispatcher pro servlet, který později vytvoříme a volá se metoda forward, aby se request na tento servlet předal ke zpracování.

  • src
    • main
      • java
      • resources
      • webapp
    • test
      • java
      • resources
  • target
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>io.github.jirkasa</groupId>
    <artifactId>metoda-forward</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>
    <name>metoda-forward</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>17</java.version>
        <maven.compiler.source>${java.version}</maven.compiler.source>
        <maven.compiler.target>${java.version}</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
</project>
import java.io.IOException;
import java.util.Random;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class NahodneCisloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
        Random rand = new Random();
        // vygenerování náhodného čísla
        int randomNumber = rand.nextInt(100);
        
        // získání request dispatcheru pro ZobrazCisloServlet
        RequestDispatcher dispatcher = req.getRequestDispatcher("/zobraz-cislo");
        
        // aby měl k vygenerovanému číslu druhý servlet přístup, tak jej nastavíme jako atribut
        req.setAttribute("nahodneCislo", randomNumber);
        
        // předání requestu pro zpracování servletem ZobrazCisloServlet
        dispatcher.forward(req, res);
    }
}

Teď můžeme vytvořit servlet, na který se v prvním servletu volá metoda forward. Pojmenujeme jej jako ZobrazCisloServlet. Tento servlet jen získá atribut "nahodneCislo" pomocí metody getAttribute, který nastavil předchozí servlet a vypíše jej na stránku.

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class ZobrazCisloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
        // získání atributu, který nastavil předchozí servlet
        int nahodneCislo = (int) req.getAttribute("nahodneCislo");
        
        res.setContentType("text/html; charset=utf-8");
        try (PrintWriter out = res.getWriter()) {
            out.println("<!DOCTYPE html>");
            out.println("<html>");
            out.println("<head>");
            out.println("<meta charset=\"UTF-8\">");
            out.println("<title>Metoda forward</title>");
            out.println("</head>");
            out.println("<body>");
            out.println("<h1>Náhodné číslo je: " + nahodneCislo + "</h1>");
            out.println("</body>");
            out.println("</html>");
        }
    }
}

Na závěr ještě vytvoříme soubor web.xml a servlety namapujeme.

  • src/main/webapp/WEB-INF
  • src/main/webapp/WEB-INF
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://Java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" 
id="WebApp_ID" version="3.0">
    <servlet>
        <servlet-name>NahodneCislo</servlet-name>
        <servlet-class>NahodneCisloServlet</servlet-class>
    </servlet>
    <servlet>
        <servlet-name>ZobrazCislo</servlet-name>
        <servlet-class>ZobrazCisloServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>NahodneCislo</servlet-name>
        <url-pattern>/nahodne-cislo</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>ZobrazCislo</servlet-name>
        <url-pattern>/zobraz-cislo</url-pattern>
    </servlet-mapping>
</web-app>

Po spuštění aplikace a otevření http://localhost:8080/metoda-forward/nahodne-cislo byste měli vidět stránku s vygenerovaným náhodným číslem.

Stránka s náhodným číslem

Naše aplikace obsahuje jeden problém. Pokud navštívíme http://localhost:8080/metoda-forward/zobraz-cislo (servlet ZobrazCisloServlet), tak nám server vrátí chybu (response se status kódem 500). Atribut "nahodneCislo" totiž není nastavený a metoda getAttribute tedy vrátí null.

Chybová stránka

Nechceme aby servlet ZobrazCisloServlet mohli uživatelé zavolat přímo. Je pouze používán jinými servlety. V příští části, která je o filtrech, se dozvíte, jak můžete k některým servletům přístup omezit.

Metoda include

Jak použít metodu forward jsme si ukázali. Teď si vytvoříme projekt, kde použijeme metodu include. Ta slouží k tomu, že můžeme request předat ke zpracování do jiného servletu, který nám může třeba něco nastavit do response, a poté pokračovat ve zpracování requestu.

Pro ukázku si vytvoříme servlet, který bude vytvářet HTML stránku voláním různých dalších servletů. Jeden servlet vytvoří začátek stránky, další její obsah a poslední konec stránky. Začneme tím, že si založíme nový Maven projekt a vytvoříme servlet, který pojmenujeme třeba jako StrankaServlet. Tento servlet bude používat ostatní servlety pro sestavení stránky. Následující ukázka ukazuje jeho kód. Je úplně jednoduchý. Jen se získá RequestDispatcher pro jednotlivé servlety a zavolá se metoda include.

  • src
    • main
      • java
      • resources
      • webapp
    • test
      • java
      • resources
  • target
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>io.github.jirkasa</groupId>
    <artifactId>metoda-include</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>
    <name>metoda-include</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>17</java.version>
        <maven.compiler.source>${java.version}</maven.compiler.source>
        <maven.compiler.target>${java.version}</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
</project>
import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class StrankaServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
        // jelikož chceme print writer zavřít až poté, co jej použijí jiné servlety, tak celý kód obalíme v try-with-resources
        try (PrintWriter out = res.getWriter()) {
            res.setContentType("text/html; charset=utf-8");
            req.getRequestDispatcher("/page-start").include(req, res);
            req.getRequestDispatcher("/page-obsah").include(req, res);
            req.getRequestDispatcher("/page-end").include(req, res);			
        }
    }
}

Teď vytvoříme servlety pro sestavení jednotlivých částí stránky. Ukazují je následující ukázky.

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class PageStartServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
        // vytvoření začátku stránky
        PrintWriter out = res.getWriter();
        out.println("<!DOCTYPE html>");
        out.println("<html>");
        out.println("<head>");
        out.println("<meta charset=\"UTF-8\">");
        out.println("<title>Metoda include</title>");
        out.println("</head>");
        out.println("<body>");
    }
}
import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class PageObsahServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
        // vytvoření obsahu stránky
        PrintWriter out = res.getWriter();
        out.println("<h1>Obsah stránky</h1>");
    }
}
import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class PageEndServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
        // vytvoření konce stránky
        PrintWriter out = res.getWriter();
        out.println("</body>");
        out.println("</html>");
    }
}

Teď již zbývá jen vytvořit soubor web.xml a servlety namapovat. Ukazuje jej následující ukázka.

  • src/main/webapp/WEB-INF
  • src/main/webapp/WEB-INF
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://Java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" 
id="WebApp_ID" version="3.0">
    <servlet>
        <servlet-name>Stranka</servlet-name>
        <servlet-class>StrankaServlet</servlet-class>
    </servlet>
    <servlet>
        <servlet-name>PageStart</servlet-name>
        <servlet-class>PageStartServlet</servlet-class>
    </servlet>
    <servlet>
        <servlet-name>PageObsah</servlet-name>
        <servlet-class>PageObsahServlet</servlet-class>
    </servlet>
    <servlet>
        <servlet-name>PageEnd</servlet-name>
        <servlet-class>PageEndServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>Stranka</servlet-name>
        <url-pattern>/stranka</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>PageStart</servlet-name>
        <url-pattern>/page-start</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>PageObsah</servlet-name>
        <url-pattern>/page-obsah</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>PageEnd</servlet-name>
        <url-pattern>/page-end</url-pattern>
    </servlet-mapping>
</web-app>

Po spuštění aplikace a navštívení http://localhost:8080/metoda-include/stranka se vám zobrazí vygenerovaná stránka.

Vygenerovaná stránka

Stejně jako v ukázce pro metodu forward, tak i zde máme problém s tím, že uživatel může navštívit i servlety, které by neměl. Může navštívit třeba http://localhost:8080/metoda-include/page-start a vrátí se mu jen začátek stránky. Jak jsem již psal, tak v příští části se dozvíte, jak k některým servletům přístup omezit.

Přesměrování

Kromě toho, že můžeme komunikovat mezi servlety pomocí request dispatcheru, tak je tu i jiná možnost. Již jsme se s ní v tutoriálu potkaly. Můžeme použít metodu sendRedirect HttpServletResponse objektu k přesměrování na jinou stránku. Tato metoda posílá HTTP response se status kódem 302. Ten klientovi (prohlížeči) naznačuje, že má provést request na jinou URL (tato URL se předává v headeru jménem Location). Můžeme tedy uživatele přesměrovat na jiný servlet tím, že mu řekneme aby na něj poslal request. Nejedná se tedy o komunikaci mezi servlety na serveru, ale probíhá přes klienta.

Použití metody sendRedirect je jednoduché a již jsme ji v minulé části použili. Nebudeme tedy vytvářet žádný projekt, následující ukázka jen ukazuje její použití.

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class Servlet1 extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
        res.sendRedirect("./servlet-2");
    }
}

V této části jste se dozvěděli, jakým způsobem můžete komunikovat mezi servlety. Narazili jsme ale na problém. Nechceme uživatelům umožnit, aby měli k některým servletům přímý přístup. Proto se v příští části podíváme na filtry.