Sledování relace

V této části si ukážeme různé způsoby, jak můžeme sledovat stav uživatelské relace ve webové aplikaci. HTTP je stateless protokol. To znamená, že každý request od klienta je nezávislý a server nemá automaticky informace o předchozím stavu. Možná to moc nechápete, ale neumím to lépe popsat. Prostě chceme ukládat nějaká data o uživateli, když naši webovou aplikaci používá. Chceme třeba ukládat, co má uživatel v nákupním košíku a tak podobně. HTTP protokol funguje tak, že se jen pošle request a ze serveru se vrátí response. Nikde se žádná data neukládají. Proto musíme použít různé způsoby, jak uživatele sledovat, když naši webovou aplikaci používá a prochází více webových stránek.

Techniky sledování relace

Pro sledování relace (session tracking) existují různé techniky. Zde jsou vypsané:

  • Cookies - můžeme nastavit hodnoty, které si prohlížeč uloží a bude je automaticky posílat při každém requestu
  • URL rewriting - můžeme serveru poslat v URL query parametr a on jej může použít při generování stránky
  • Skryté inputy - do HTML formuláře můžeme umístit skryté inputy, které se pošlou na server, když se formulář odešle
  • Parametry v URL - můžeme posílat parametry jako součást URL cesty (např. https://localhost:8080/produkty/[parametr])
  • Sessions - můžeme ukládat informace o uživateli na serveru a klientovi pošleme cookie s identifikátorem, přes který se k nim dostane

URL rewriting

Jako první si ukážeme URL rewriting. Naprogramujeme si aplikaci, ve které uživatel na úvodní stránce zadá ve formuláři jméno. Když jej pošle, tak se mu zobrazí stránka, která jej pozdraví. Na této stránce bude navíc ještě odkaz na jinou stránku, která uživateli vypíše, jaké je jeho jméno. Jde o to, abychom si zkusili pomocí URL rewritingu uživatelovo jméno pamatovat napříč stránkami.

Vytvoříme si Maven projekt (jak to udělat v Eclipse je popsáno v první části tutoriálu) a servlet ZadejJmenoServlet, který uživateli pošle stránku s formulářem. Generování stránek pro nás zatím není moc komfortní. To se zlepší, až začneme používat JSP.

  • 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>url-rewriting</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>
    <name>url-rewriting</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.io.PrintWriter;

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

public class ZadejJmenoServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
        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>URL rewriting</title>");
            out.println("</head>");
            out.println("<body>");
            out.println("<h1>Zadej jméno</h1>");
            out.println("<form action=\"./pozdrav\" method=\"POST\">");
            out.println("<label>Jméno:</label>");
            out.println("<input type=\"text\" name=\"jmeno\"/>");
            out.println("<button>Odeslat</button>");
            out.println("</form>");
            out.println("</body>");
            out.println("</html>");
        }
    }
}

Vytvoříme si soubor web.xml (ve složce src/main/webapp/WEB-INF) a servlet namapujeme třeba na URL "/zadej-jmeno".

  • 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>ZadejJmeno</servlet-name>
        <servlet-class>ZadejJmenoServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>ZadejJmeno</servlet-name>
        <url-pattern>/zadej-jmeno</url-pattern>
    </servlet-mapping>
</web-app>

Po spuštění aplikace si můžete stránku zobrazit otevřením http://localhost:8080/url-rewriting/zadej-jmeno.

Stránka pro zadání jména

Teď si vytvoříme servlet pro stránku s pozdravem. Pojmenujeme jej třeba jako PozdravServlet. Budeme reagovat na requesty poslané metodou POST, protože formulář touto metodou odesíláme. Implementujeme tedy metodu doPost, ve které získáme uživatelovo jméno a zobrazíme jej na stránce kterou vygenerujeme. Také na stránku umístíme odkaz na stránku ke zjištění jména, který bude mít jako query parametr nastavený uživatelovo jméno.

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 PozdravServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
        String jmeno = req.getParameter("jmeno");
        
        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>URL rewriting</title>");
            out.println("</head>");
            out.println("<body>");
            out.println("<h1>Ahoj " + jmeno + "</h1>");
            out.println("<a href=\"./jake-je-jmeno?jmeno=" + jmeno + "\">Jaké je moje jméno?</a>");
            out.println("</body>");
            out.println("</html>");
        }
    }
}

Ve web.xml servlet namapujeme na "/pozdrav", jelikož tam směřuje formulář, který jsme vytvořili pro zadání jména.

  • 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>ZadejJmeno</servlet-name>
        <servlet-class>ZadejJmenoServlet</servlet-class>
    </servlet>
    <servlet>
        <servlet-name>Pozdrav</servlet-name>
        <servlet-class>PozdravServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>ZadejJmeno</servlet-name>
        <url-pattern>/zadej-jmeno</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>Pozdrav</servlet-name>
        <url-pattern>/pozdrav</url-pattern>
    </servlet-mapping>
</web-app>

Když teď na stránce pro zadání jména zadáme ve formuláři jméno a odešleme jej, tak se nám zobrazí stránka s pozdravem a naše jméno nám vypíše.

Stránka s pozdravem

Na stránku s pozdravem jsme také přidali odkaz na stránku, která nám jméno vypíše ještě jednou. Takže si pro ni vytvoříme servlet. Můžeme jej pojmenovat třeba jako JakeJeJmenoServlet. Následující ukázka jej ukazuje.

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 JakeJeJmenoServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
        String jmeno = req.getParameter("jmeno");
        
        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>URL rewriting</title>");
            out.println("</head>");
            out.println("<body>");
            out.println("<h1>Tvoje jméno je: " + jmeno + "</h1>");
            out.println("</body>");
            out.println("</html>");
        }
    }
}

V souboru web.xml můžeme servlet namapovat na "/jake-je-jmeno".

  • 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>ZadejJmeno</servlet-name>
        <servlet-class>ZadejJmenoServlet</servlet-class>
    </servlet>
    <servlet>
        <servlet-name>Pozdrav</servlet-name>
        <servlet-class>PozdravServlet</servlet-class>
    </servlet>
    <servlet>
        <servlet-name>JakeJeJmeno</servlet-name>
        <servlet-class>JakeJeJmenoServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>ZadejJmeno</servlet-name>
        <url-pattern>/zadej-jmeno</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>Pozdrav</servlet-name>
        <url-pattern>/pozdrav</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>JakeJeJmeno</servlet-name>
        <url-pattern>/jake-je-jmeno</url-pattern>
    </servlet-mapping>
</web-app>

Pokud teď kliknete na odkaz na stránce s pozdravem, tak se vám otevře stránka, kterou ukazuje následující obrázek, a zobrazí se vám zadané jméno.

Stránka zobrazující jméno

O čemu URL rewriting je, vám je teď už asi jasné. Je to o tom, že do odkazů nastavujeme query parametry a ukládáme tím tedy informace mezi stránkami.

Skryté inputy

Jako další techniku pro sledování relace si ukážeme použití skrytých inputů ve formulářích. Pro jeho vyzkoušení nám stačí upravit třídu PozdravServlet z ukázky pro url-rewriting. Namísto odkazu na stránku přidáme formulář se skrytým inputem (nastavíme mu atribut type na hidden) a tlačítkem pro jeho odeslání. Formulář se bude posílat metodou GET abychom nemuseli upravovat i třídu JakeJeJmenoServlet. To znamená, že se hodnoty formuláře budou posílat jako query string v URL (výsledné URL requestu bude stejné jako u odkazu).

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 PozdravServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
        String jmeno = req.getParameter("jmeno");
        
        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>URL rewriting</title>");
            out.println("</head>");
            out.println("<body>");
            out.println("<h1>Ahoj " + jmeno + "</h1>");
            out.println("<form action=\"./jake-je-jmeno\" method=\"GET\">");
            out.println("<input type=\"hidden\" name=\"jmeno\" value=\"" + jmeno + "\">");
            out.println("<button>Jaké je moje jméno?</button>");
            out.println("</form>");
            out.println("</body>");
            out.println("</html>");
        }
    }
}

Následující obrázek ukazuje, jak teď bude stránka s pozdravem vypadat. Tlačítko se chová úplně stejně jako předtím odkaz.

Předělaná stránka s pozdravem

Parametry v URL

Další věcí, kterou si ukážeme, jsou parametry v URL cestě. V tomto případě nebudeme upravovat předchozí projekt, ale vytvoříme si nový. Pro ukázku nám bude stačit vytvořit jen jeden servlet. Parametr předáme přímo v URL, když si budeme stránku otevírat. Můžeme jej pojmenovat třeba jako MujServlet. Bude sloužit jen k tomu, že vypíše URL parametr na stránku. Nejdříve si ale v tomto případě vytvoříme soubor web.xml, kde si servlet namapujeme pro URL pattern. Nastavíme mu url-pattern na "/muj-servlet/*". To znamená, že se zavolá pro všechno, co začíná na "/muj-servlet". Budeme tedy schopni předat URL parametr.

  • 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>url-parametry</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>
    <name>url-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">
    <servlet>
        <servlet-name>MujServlet</servlet-name>
        <servlet-class>MujServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>MujServlet</servlet-name>
        <url-pattern>/muj-servlet/*</url-pattern>
    </servlet-mapping>
</web-app>

Teď můžeme vytvořit servlet. Ukazuje jej následující ukázka. Pro získání části cesty, která se nachází za částí "/muj-servlet" používáme metodu getPathInfo. Například pro "https://localhost:8080/muj-servlet/neco/aaa" vrátí "/neco/aaa".

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 MujServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
        // získání části cesty nacházející se za /muj-servlet
        String pathInfo = req.getPathInfo();
        
        String parametr;
        if (pathInfo != null) {
            // získání parametru z cesty (cesta může být třeba "/neco/aaa")
            parametr = pathInfo.split("/")[1];
        } else {
            parametr = "";
        }
        
        // vygenerování stránky, která zobrazí parametr (a také proměnnou pathInfo)
        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>URL parametry</title>");
            out.println("</head>");
            out.println("<body>");
            out.println("<h1>pathInfo: " + pathInfo + "</h1>");
            out.println("<h1>URL parametr: " + parametr + "</h1>");
            out.println("</body>");
            out.println("</html>");
        }
    }
}

Pokud si aplikaci spustíte a navštívíte třeba http://localhost:8080/url-parametry/muj-servlet/muj-text, tak uvidíte úplně stejnou stránku, kterou ukazuje následující obrázek.

Stránka zobrazující URL parametr

Cookies

Předchozí techniky, které jsme si ukázali, jsou spíš jen takové mechanismy pro předání informací z jedné stránky na jinou. Pokud ale chceme o uživateli naší webové aplikace uložit nějaká data dlouhodoběji a používat je při každém requestu, který pošle na server, tak existují jiné možnosti (v podstatě asi dvě). Jedna z nich jsou cookies.

Cookies jsou malé textové soubory, které si klient (prohlížeč) ukládá na lokální disk a posílá je na server spolu s requesty, které posílá. Mají hodnoty ve formě klíče a hodnoty. Jsou vytvořeny serverem a posílány jen na server, který je vytvořil. Cookies server u klienta vytváří pomocí headeru "Set-Cookie", který posílá v HTTP odpovědi. Klient si tento header přečte a cookie uloží. Poté jej na server předává při každém requestu pomocí headeru "Cookie".

Kromě hodnoty můžeme pro cookie nastavit i další vlastnosti. Co všechno můžeme nastavit, popisuje následující seznam:

  • Domain - určuje doménu, pro kterou se má cookie nastavit (jde použít jen aktuální doména, nebo doména vyššího řádu)
  • Expires - nastavuje datum, kdy má vypršet platnost cookie (kdy se má odstranit)
  • HttpOnly - nastavuje, že ke cookie nebude mít přístup JavaScript
  • Max-Age - nastavuje počet sekund, po kterých vyprší platnost cookie (kdy se odstraní) - pokud je nastaveno Expires i Max-Age, tak má přednost Max-Age.
  • Partitioned - nevím k čemu to slouží
  • Path - nastavuje cestu, která musí existovat v URL, aby se cookie poslala
  • SameSite - určuje, jestli se má cookie použít i pro cross-site requsty a poskytuje určitou ochranu proti CSRF útokům
  • Secure - určuje, jestli se má cookie posílat jen pro requesty poslané přes HTTPS

Jak může "Set-Cookie" header vypadat, ukazuje následující ukázka:

Set-Cookie: nazev=hodnota; SameSite=None; Secure

Práce s cookies v servletech

V servletech máme pro vytváření cookies třídu Cookie. Nemusíme tedy header "Set-Cookie" nastavovat ručně sami. Stačí vytvořit instanci této třídy a zavolat metodu addCookie HttpServletResponse objektu, které vytvořenou cookie předáme.

Pro ukázku práce s cookies si vytvoříme podobnou aplikaci, kterou jsme si vytvořili pro ukázku URL rewritingu. V tomto případě ale nebudeme jméno předávat nikde v URL, ale pomocí cookie. Vytvoříme nový Maven projekt a servlet pro stránku na zadání jména, který můžeme pojmenovat třeba jako ZadejJmenoServlet. Bude v podstatě stejný jako servlet z předchozí aplikace, jen atribut action formuláře bude nastaven na "./jmeno". Ukazuje jej následující ukázka.

  • 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>cookies</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>
    <name>cookies</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.io.PrintWriter;

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

public class ZadejJmenoServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
        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>Cookies</title>");
            out.println("</head>");
            out.println("<body>");
            out.println("<h1>Zadej jméno</h1>");
            out.println("<form action=\"./jmeno\" method=\"POST\">");
            out.println("<label>Jméno:</label>");
            out.println("<input type=\"text\" name=\"jmeno\"/>");
            out.println("<button>Odeslat</button>");
            out.println("</form>");
            out.println("</body>");
            out.println("</html>");
        }
    }
}

Servlet můžeme v souboru web.xml namapovat třeba opět na "/zadej-jmeno".

  • 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>ZadejJmeno</servlet-name>
        <servlet-class>ZadejJmenoServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>ZadejJmeno</servlet-name>
        <url-pattern>/zadej-jmeno</url-pattern>
    </servlet-mapping>
</web-app>

Po spuštění aplikace a navštívení http://localhost:8080/cookies/zadej-jmeno uvidíte stránku, kterou ukazuje následující obrázek.

Stránka pro zadání jména

Teď si vytvoříme servlet, který zpracuje formulář poslaný metodou POST a také pošle stránku, na které jméno zobrazí, když obdrží GET request. Pojmenujeme jej jako JmenoServlet. Následující ukázka ukazuje implementaci metody doPost, která získá z poslaného formuláře jméno a uloží jej do cookie. Poté uživatele přesměruje na stránku, kde se mu uživatelské jméno zobrazí. Používá se metoda sendRedirect, která vlastně namísto stránky pošle HTTP response se status kódem 302. Ten prohlížeči říká, že má provést HTTP request na jinou URL.

import java.io.IOException;

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

public class JmenoServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
        String jmeno = req.getParameter("jmeno");

        Cookie cookie = new Cookie("jmeno", jmeno);
        res.addCookie(cookie);

        res.sendRedirect("./jmeno");
    }
}

V metodě doGet si hodnotu cookie přečteme a pokud existuje, tak ji zobrazíme na stránce. Kromě toho na stránku přidáme odkaz na nastavení nového jména (nové hodnoty cookie). Následující ukázka metodu doGet ukazuje. Pro získání všech cookies se používá metoda getCookies, která vrací pole nebo null. V tomto poli poté hledáme příslušnou cookie, která obsahuje hodnotu pro jméno.

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

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

public class JmenoServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
        String jmeno = req.getParameter("jmeno");
        
        Cookie cookie = new Cookie("jmeno", jmeno);
        res.addCookie(cookie);
        
        res.sendRedirect("./jmeno");
    }
    
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
        Cookie[] cookies = req.getCookies();
        
        String jmeno = null;
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if (cookie.getName().equals("jmeno")) {
                    jmeno = cookie.getValue();
                }
            }
        }
        
        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>Cookies</title>");
            out.println("</head>");
            out.println("<body>");
            if (jmeno != null) {
                out.println("<h1>Zadané jméno: " + jmeno + "</h1>");
            } else {
                out.println("<h1>Není zadané žádné jméno</h1>");
            }
            out.println("<a href=\"./zadej-jmeno\">Zadat nové jméno</a>");
            out.println("</body>");
            out.println("</html>");
        }
    }
}

V souboru web.xml servlet namapujeme na "/jmeno".

  • 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>ZadejJmeno</servlet-name>
        <servlet-class>ZadejJmenoServlet</servlet-class>
    </servlet>
    <servlet>
        <servlet-name>Jmeno</servlet-name>
        <servlet-class>JmenoServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>ZadejJmeno</servlet-name>
        <url-pattern>/zadej-jmeno</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>Jmeno</servlet-name>
        <url-pattern>/jmeno</url-pattern>
    </servlet-mapping>
</web-app>

Po vyplnění jména se vám nyní otevře stránka http://localhost:8080/cookies/jmeno, na které se vám vaše zadané jméno zobrazí.

Stránka se zadaným jménem

Když to shrneme, tak cookies jsou zjednodušeně hodnoty, které může server klientovi nastavit, aby je při každém requestu na server posílal.

Sessions

Cookies se ukládají u klienta. Je tedy možné, že si může jejich hodnoty změnit, aniž by k tomu dostal příkaz ze serveru. Alternativa je ukládat data uživatele na serveru, kde je již jen tak modifikovat nemůže. Klient akorát k datům, které mu patří, dostane unikátní identifikátor ve formě hodnoty v cookie. Tuto cookie klient posílá při každém requestu a server podle něj najde data, která s ním jsou spjata.

Pro ukládání dat uživatele na serveru (v session) se používá objekt typu HttpSession. Můžeme jej získat na HttpServletRequest objektu zavoláním metody getSession. Tato metoda vrací session, která je spjata s klientem, který poslal request. Pokud session ještě není vytvořena (metoda getSession se volá poprvé), tak se nejprve vytvoří a až poté vrátí. Následující tabulka popisuje metody, které u HttpSession objektu můžeme použít.

MetodaNávratový typPopis
getAttribute(String name)ObjectVrací objekt uložený v session podle předaného jména.
getAttributeNames()Enumeration<String>Vrací enumeraci všech jmen, pod kterými jsou v session uloženy objekty.
getCreationTime()longVrací čas, kdy byla session vytvořena, v milisekundách od půlnoci 1. ledna 1970 GMT.
getId()StringVrací unikátní identifikátor, vytvořený pro tuto session.
getLastAccessedTime()longVrací čas, kdy naposled klient poslal na server request spjatý s touto session, v milisekundách od půlnoci 1. ledna 1970 GMT.
getMaxInactiveInterval()intVrací maximální časový interval v sekundách, po který session existuje, pokud klient do té doby neposlal na server žádný request.
getServletContext()ServletContextVrací ServletContext, do kterého session patří.
invalidate()voidZruší session.
isNew()booleanVrací true, pokud je session nově vytvořená a klient o ní ještě neví.
removeAttribute(String name)voidOdstraní objekt, uložený v session, podle předaného jména.
setAttribute(String name, Object value)voidUkládá objekt do session pod předaným jménem.
setMaxInactiveInterval(int interval)voidNastavuje maximální časový interval v sekundách, po který session existuje, pokud klient do té doby neposlal na server žádný request.

Ukázka použití session

Pro ukázku práce se session si vytvoříme aplikaci, ve které bude mít uživatel tabulku, do které si bude moci přidávat nové řádky. Vytvoříme si nový Maven projekt, který můžeme pojmenovat třeba jako tabulka. Pro stránku s tabulkou si poté vytvoříme servlet TabulkaServlet. Ukazuje jej následující ukázka. Tento servlet jen získá položky ze session a zobrazí je na stránce v tabulce. Položky se ukládají v LinkedListu. Do session můžeme uložit v podstatě cokoliv, jelikož všechny třídy v Javě implicitně dědí od třídy Object. Při získávání objektu ze session za použití getAttribute metody jej ale musíme castnout, jelikož vrací instanci třídy Object.

  • 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>tabulka</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>
    <name>tabulka</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.io.PrintWriter;
import java.util.LinkedList;

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

public class TabulkaServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
        // získání session pro klienta, který request posílá
        HttpSession session = req.getSession();
        
        // získání objektu uloženého v session pod názvem "polozky"
        LinkedList<String> polozky = (LinkedList<String>) session.getAttribute("polozky");
        
        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>Tabulka</title>");
            out.println("</head>");
            out.println("<body>");
            out.println("<h1>Tabulka</h1>");
            out.println("<table>");
            out.println("<tr><th>Položky</th></tr>");
            if (polozky != null) {
                // vypsání všech položek do tabulky
                for (String polozka : polozky) {
                    out.println("<tr><td>" + polozka + "</td></tr>");
                }
            }
            out.println("</table>");
            out.println("<a href=\"./pridat-polozku\">Přidat novou položku</a>");
            out.println("</body>");
            out.println("</html>");
        }
    }
}

Můžeme si vytvořit soubor web.xml a servlet namapovat třeba na "/polozky".

  • 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>Tabulka</servlet-name>
        <servlet-class>TabulkaServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>Tabulka</servlet-name>
        <url-pattern>/polozky</url-pattern>
    </servlet-mapping>
</web-app>

Po spuštění aplikace a navštívení http://localhost:8080/tabulka/polozky, uvidíte stránku s tabulkou, která zatím neobsahuje žádná data. Tabulka nemá žádné ohraničení, jelikož nepoužíváme žádné CSS styly.

Stránka s tabulkou

Pokud se po otevření stránky podívate do vývojářských nástrojů na cookies, tak tam uvidíte cookie pro unikátní identifikátor session. Podle tohoto identifikátoru server pozná, jaká session nám patří.

Stránka s tabulkou

Pro přidání položky si vytvoříme servlet PridatPolozkuServlet. Následující ukázka ukazuje implementaci doGet metody, která jen vyrenderuje stránku s formulářem.

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 PridatPolozkuServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
        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>Tabulka</title>");
            out.println("</head>");
            out.println("<body>");
            out.println("<h1>Přidat položku</h1>");
            out.println("<form action=\"./pridat-polozku\" method=\"POST\">");
            out.println("<input type=\"text\"/ name=\"polozka\">");
            out.println("<button>Přidat</button>");
            out.println("</form>");
            out.println("<a href=\"./polozky\">Zpět na tabulku</a>");
            out.println("</body>");
            out.println("</html>");
        }
    }
}

Servlet pro přidání položky si namapujeme na "/pridat-polozku, jelikož tam odkazujeme na stránce s tabulkou.

  • 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>Tabulka</servlet-name>
        <servlet-class>TabulkaServlet</servlet-class>
    </servlet>
    <servlet>
        <servlet-name>PridatPolozku</servlet-name>
        <servlet-class>PridatPolozkuServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>Tabulka</servlet-name>
        <url-pattern>/polozky</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>PridatPolozku</servlet-name>
        <url-pattern>/pridat-polozku</url-pattern>
    </servlet-mapping>
</web-app>

Pokud teď na stránce s tabulkou kliknete na odkaz "Přidat novou položku", tak se vám zobrazí následující stránka s formulářem pro přidání nové položky.

Stránka pro přidání položky

Formulář se posílá na "./pridat-polozku" metodou POST. V servletu PridatPolozkuServlet tedy implementujeme metodu doPost a odeslaný formulář v ní zpracujeme. Přidáme zadanou položku do session a uživatele přesměrujeme na stránku s tabulkou. Následující ukázka implementaci metody doPost ukazuje.

import java.io.IOException;
import java.io.PrintWriter;
import java.util.LinkedList;

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

public class PridatPolozkuServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
        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("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />");
            out.println("<title>Tabulka</title>");
            out.println("</head>");
            out.println("<body>");
            out.println("<h1>Přidat položku</h1>");
            out.println("<form action=\"./pridat-polozku\" method=\"POST\">");
            out.println("<input type=\"text\"/ name=\"polozka\">");
            out.println("<button>Přidat</button>");
            out.println("</form>");
            out.println("<a href=\"./polozky\">Zpět na tabulku</a>");
            out.println("</body>");
            out.println("</html>");
        }
    }
    
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
        // z nějakého důvodu je potřeba nastavit encoding, jinak bychom mohli získat z formuláře text ve špatném formátu
        req.setCharacterEncoding("UTF-8");
        
        // získání zadaného textu z formuláře
        String polozka = req.getParameter("polozka");
        
        HttpSession session = req.getSession();
        
        // získání uloženého listu položek
        LinkedList<String> polozky = (LinkedList<String>) session.getAttribute("polozky");
        // pokud list ještě neexistuje, tak jej vytvoříme a uložíme v session
        if (polozky == null) {
            polozky = new LinkedList<String>();
            session.setAttribute("polozky", polozky);
        }
        
        // přidání položky do listu
        polozky.add(polozka);
        
        // přesměrování na stránku s tabulkou
        res.sendRedirect("./polozky");
    }
}

Naše aplikace je hotová. Můžete si do tabulky zkusit přidat pár položek.

Stránka s tabulkou, která obsahuje vytvořené položky

Pokud jste si do tabulky pár položek přidali a otevřete si aplikaci v jiném prohlížeči nebo alespoň v anonymním okně, tak tam přidané položky neuvidíte, jelikož se vytvoří úplně nová session.

Je nutné si uvědomit, že session neslouží k permanentnímu uložení dat. Pokud totiž server restartujeme, tak se všechny session smažou. Navíc uživatel k session nemá přístup navždy. Po nějaké době se mu unikátní identifikátor pro session uložený jako cookie smaže. Defaultně se smaže, když uživatel zavře prohlížeč, jelikož pro cookie není nastavena Max-Age ani Expires vlastnost.

Session cookie

Defaultní maximální čas, do kdy session na serveru existuje, pokud klient neposlal na server request spjatý se session, je v Tomcatu 30 minut. U jednotlivých session tento čas můžeme změnit pomocí metody setMaxInactiveInterval. Pokud jej ale chceme změnit pro všechny session, můžeme to udělat pomocí session-config elementu v souboru web.xml, jak ukazuje následující ukázka. Čas se nastavuje v minutách.

<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">
    <session-config>
        <session-timeout>20</session-timeout>
    </session-config>
</web-app>

Pro tuto část je to vše. Nyní již víte, jak můžete uživatele ve své webové aplikaci sledovat. V příští části si ukážeme, jak funguje komunikace mezi servlety.