Úvod do webových aplikací

Z FI WIKI
Přejít na: navigace, hledání


Webové aplikace jsou probírány podrobně v předmětu PA165, pro který existují podrobně zpracované materiály pod heslem Kategorie:Webové aplikace.

V předmětu PV168 není prostor probrat webové aplikace dostatečně podrobně. Podobně jako se v semináři probírá JDBC, a ORM nástroje jsou nechány do předmětu PA165, probereme zde jen pevné základy, které se v čase nemění, a různé nadstavby a frameworky, jejichž obliba se mění z roku na rok, ponecháme do PA165.

Tato stránka obsahuje stručnou teorii a tři praktické ukázky, každou ve verzi pro IntelliJ a ve verzi pro NetBeans. Probrání této stránky by mělo vystačit na jeden dvouhodinový seminář.

Úvod

Webové aplikace jsou aplikace, kde uživatelským rozhraním je webový prohlížeč. Mají tři obrovské výhody oproti ostatním typům aplikací (desktopové, mobilní, příkazový řádek):

  • uživatel nemusí nic instalovat
  • vývojář se stará jen o jednu verzi aplikace
  • u správně napsaných aplikací existují adresy URL do částí aplikace, např. lze někomu jinému poslat odkaz na stránku s popisem určitého zboží

Podrobný popis je v článku Webové aplikace.

Webové aplikace na platformě Java

Platforma Java je pro tvorbu serverové části webových aplikací poměrně vhodná, oproti např. PHP umožňuje psát snadněji udržovatelné aplikace, dobře škáluje, má skvěle zvládnutou internacionalizaci.

Cenou je o něco náročnější učení.

Základem serverové části webových aplikací na platformě Java jsou tzv. Servlety, což jsou třídy obsluhující požadavky HTTP protokolu. Ty tvoří nejnižší vrstvu, nad kterou pak existují další nadstavby. Dalšími vrstvami jsou

  • Java Server Pages (JSP) - obdoba PHP a dalších server-side scripting nástrojů
  • Web Application Frameworks - rámce pro tvorbu složitějsích aplikací
    • rámce pro šablony stránek (Freemarker, WebMacro, Velocity, JSP+JSTL)
    • rámce pro řízení průchodu aplikací (Stripes, Struts)
    • rámce pro tvorbu stránek z komponent (Tapestry, WebWork, JSF)

Rozhraní servletů se časem téměř nemění, naopak webové aplikační rámce podléhají rychlému vývoji i módě.

Servlet API

Servlet

Servletem je každá Java třída, která implementuje interface javax.servlet.Servlet. Protože ale z praktického hlediska má smysl uvažovat pouze servlety obsluhující protokol HTTP, je důležitější vědět, že HTTP servletem je každá třída, která je potomkem třídy javax.servlet.http.HttpServlet.

Obsluha HTTP požadavků servlety je popsána v JavaServlets#Obsluha HTTP pomocí servletů, nemá smysl ji sem opisovat, prosím přečtete si ji tam.

Servlety jsou organizovány do tzv. aplikací, kdy skupina servletů je sdružena v jednom balíčku s příponou .war, což je soubor typu .jar (tedy ZIP archiv). V něm musí být přítomen soubor WEB-INF/web.xml, který popisuje konfiguraci aplikace, zejména mapování URL na servlety.

Podrobnosti viz část JavaServlets#Webová aplikace a kontext servletu, prosím přečtěte si ji.

Důležitá věc, kterou Servlet API poskytuje, je session. Jedná se vlastně o HashMap, do které je možné si ukládat hodnoty mezi jednotlivými požadavky od stejného prohlížeče. Session je udržována pomocí cookies, nebo v případě vypnutých cookies je její identifikátor přidáván do všech generovaných URL.

Teď je asi vhodný čas zkusit si vytvořit servlet.

Praktická ukázka v IntelliJ IDEA

Přidejte si do cesty JDK a IntelliJ jako obvykle a spusťte IntelliJ:

module add jdk-1.8.0 idea-15

Vytvořte si nový projekt, jak typ zvolte "Maven module". Do vygenerovaného souboru pom.xml přidejte definici verze javy 8, deklaraci nepotřebnosti souboru web.xml (v Servlet API 3.0 není potřeba) a kompilační závislost na Servlet API 3.0.

 
    <packaging>war</packaging>
    <build>
        <plugins>
            <!-- Java language version -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.5</version>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>
        </plugins>
    </build>
 
    <dependencies>       
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-web-api</artifactId>
            <version>6.0</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

Přidání Tomcat 8 do IntelliJ

Protože je to naše první webová aplikace, musíme přidat servlet container. Klikněte v menu na Run - Edit configurations, klikněte na symbol +, zvolte Tomcat Server - Local. V poli Name zvolte jiné jméno než Unnamed, třeba Tomcat8. Na záložce Server vedle položky Application Server klikněte na Configure, vyberte adresář s instalací tomcatu /packages/run.64/tomcat-8/current. (Pokud používáte vlastní notebook, stáhněte a nainstalujte si Tomcat 8 z http://tomcat.apache.org/.)

Dole bude svítit varování Warning: no artifacts marked for deployment, klikněte na Fix, vyberte Web Application: exploded a potvrďte OK.

Spuštění aplikace

Abychom mohli aplikaci zkusit spustit, potřebujeme v ní alespoň jednu webovou stránku. Vytvořte adresář src/main/webapp a v něm soubor index.jsp, do kterého napište libovolný text.

Spusťte webovou aplikaci kliknutím na zelenou šipku nebo zmáčknutím Shift+F10. Měl by se automaticky spustit Tomcat, v něm aplikace a otevřít browser text, který jste napsali do index.jsp.

První servlet

Vytvořte v adresáři src/main/java nový package cz.muni.fi.pv168.web. (Vytvoření package je nutné, třídy v default package nejsou viditelné z tříd v ne-default package !) V package vytvořte třídu UkazkovyServlet s následujícím obsahem:

 
package cz.muni.fi.pv168.web;
 
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
 
@WebServlet(urlPatterns = {"/muj/*", "*.muj"})
public class UkazkovyServlet extends HttpServlet {
 
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
 
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();
        out.println("<h1>Muj prvni servlet</h1>");
 
        int n = 10;
        String np = request.getParameter("n");
        if (np != null) n = Integer.parseInt(np);
 
        for (int i = 0; i < n; i++) {
            out.println("i=" + i + "<br/>");
        }
    }
 
}

Využíváme v této ukázce nového rysu Servlet API 3.0, kdy mapování servletu na URL je možné zadat pomocí anotace @WebServlet. V předchozích verzích bylo nutné na to vytvořit speciální tzv. deployment descriptor v souboru WEB-INF/web.xml a v něm mapování popsat:

 
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
         version="2.4">
    <servlet>
        <servlet-name>s1</servlet-name>
        <servlet-class>cz.muni.fi.pv168.web.UkazkovyServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>s1</servlet-name>
        <url-pattern>/muj/*</url-pattern>
        <url-pattern>*.muj</url-pattern>
    </servlet-mapping>
</web-app>

Spusťte znovu aplikaci, náš nově vytvořený servlet zavoláte navštívením URL http://localhost:8080/muj/

Praktická ukázka v NetBeans

module add jdk netbeans

V NetBeans vytvořte nový projekt typu Web Application (File - New project - Categories: Web - Projects: Web Application). Server zvolte Tomcat, Java EE Version zvolte JavaEE 6.

Přidání Tomcat do NetBeans

Při vytvoření prvního projektu webové aplikace NetBeans požádají o přidání prvního servletového kontejneru. Vyberte Tomcat nainstalovaný v adresáři /packages/run.64/netbeans-8.1/apache-tomcat-8.0.27 (pokud používáte vlastní notebook, stáhněte si a nainstalujte Tomcat do nějakého snadno přístupného adresáře, třeba C:\tomcat8).

Zaškrtněte volbu Use Private Configuration Folder (Catalina base) a vyberte si prázdný adresář, do kterého můžete zapisovat. Viz obrázek:

Netbeans tomcat config.png

Zvolte si nějaké jméno a heslo pro administrátora Tomcatu, je potřeba při přidávání webových aplikací.

Spuštění aplikace

NetBeans do nového projektu s webovou aplikací automaticky přidají soubor index.jsp a do něj vygenerují text Hello, world !, takže můžeme rovnou spustit aplikaci. Zkontrolujte si, že máte v NetBeans nastaven svůj oblíbený webový prohlížeč, a pak aplikaci spusťte kliknutím na zelenou šipku.

První servlet

V projektu vytvořte nový servlet zvaný UkazkovyServlet. Pravým tlačítkem klikněte na Source packages, dejte New - Other - Categories: Web - File Types: Servlet, Class Name dejte UkazkovyServlet a package dejte cz.muni.fi.pv168. Je nutné zvolit nějaký package, jinak nebude servlet spustitelný !!! Ponechte nabídnuté nastavení mapování servletu a potvrdte vytvoření servletu.

Naeditujte text do následující podoby:

 
package cz.muni.fi.pv168.web;
 
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
 
@WebServlet(urlPatterns = {"/muj/*", "*.muj"})
public class UkazkovyServlet extends HttpServlet {
 
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
 
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();
        out.println("<h1>Muj prvni servlet</h1>");
 
        int n = 10;
        String np = request.getParameter("n");
        if (np != null) n = Integer.parseInt(np);
 
        for (int i = 0; i < n; i++) {
            out.println("i=" + i + "<br/>");
        }
    }
 
}

Pozor, NetBeans se snaží skrýt metody doGet() a doPost() do sbaleného bloku, nenechte se tím zmást.

Spusťte aplikaci. Servlet zavoláte na URL http://localhost:8080/WebApplication1/UkazkovyServlet.

Můžete přidat parametr n pomocí http://localhost:8080/WebApplication1/UkazkovyServlet?n=6.

ServletContextListener

U webové aplikace nás můžou kromě obsluhy příchozích HTTP požadavků zajímat i jiné události, třeba start nebo ukončení celé aplikace, změny HttpSession a podobně, k tomu existují různé listenery.

Ukážeme si příklad na ServletContextListener, který je upozorněn na start a konec celé aplikace, takže může třeba inicializovat a uklízet spojení na databázi a podobně.

Od Servlet API 3.0 stačí příslušnou třídu anotovat anotací @WebListener:

 
package cz.muni.fi.pv168.web;
 
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
 
@WebListener
public class StartListener implements ServletContextListener {
 
    @Override
    public void contextInitialized(ServletContextEvent ev) {
        System.out.println("aplikace inicializována");
        ServletContext servletContext = ev.getServletContext();
    }
 
    @Override
    public void contextDestroyed(ServletContextEvent ev) {
        System.out.println("aplikace končí");
    }
}

Ve starších verzích Servlet API je nutné vytvořit soubor WEB-INF/web.xml a do něj dát deklaraci listeneru:

 
   <listener>
        <listener-class>cz.muni.fi.pv168.web.StartListener</listener-class>
    </listener>

Filter

Kromě servletů a listenerů můžeme definovat ještě Filtery:

 
package cz.muni.fi.pv168.web;
 
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
 
@WebFilter("/*")
public class MujFilter implements Filter {
 
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("filter inicializován");
    }
 
    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        System.out.println("požadavek na "+((HttpServletRequest)req).getRequestURI());
        chain.doFilter(req,res);
    }
 
    @Override
    public void destroy() {
    }
}

V Servlet API 3.0 je stačí označit anotací @WebFilter, u starších verzí je třeba do web.xml dodat deklaraci

 
    <filter>
        <filter-name>MujFilter</filter-name>
        <filter-class>cz.muni.fi.pv168.web.MujFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>MujFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

což je podstatně zdlouhavější.

Filtry lze výhodně použít třeba při vytváření webových frameworků, nebo pro autentizaci (heslem, certifikátem, atd.).

JSP

Psát delší sekvence HTML v servletech je poměrně nepohodlné, proto každý servlet container poskytuje tzv. Java Server Pages - JSP.

Jedná se o psaní servletů naruby - každý soubor s příponou .jsp je před spuštěním konvertován na servlet, který je za běhu překompilován a spuštěn.

Do JSP lze psát přímo Java kód, tzv. scriptlety:

  • běžný kód je možné psát mezi znaky <% a %>, je umístěn do metody service() servletu
  • tisk výrazu lze napsat pomocí <%= a %>, je to ekvivalentní <%out.print(vyraz)%>
  • kód mimo metodu service() lze napsat pomocí <%! a %>.

Podrobná dokumentace viz JSP 1.2 syntax card.

Nejlépe je to vidět na ukázce.

  • V IntelliJ naeditujte soubor src/main/webapp/index.jsp
  • v NetBeans naeditujte soubor index.jsp v sekci Web pages

a do něho napište:

 
<%@ page contentType="text/html; charset=utf-8" pageEncoding="utf-8" %>
 
<%! //kód mimo metodu service()
   public String prictiJedna(String a) {
       return Integer.toString(Integer.parseInt(a)+1);
   }
%>
<html>
<body>
  <%  //kód uvnitř metody service()
      if(request.getMethod().equals("GET")) {
  %>
  <form method="post">
      Zadejte číslo: <input name="cislo" value="">
      <input type="submit" >
  </form>
  <%
      } else if (request.getMethod().equals("POST")) {
          String cislo = request.getParameter("cislo");
          String plusjedna = prictiJedna(cislo);
   %>
      Výsledek <%out.println(cislo);%> + 1 je <%=plusjedna%> 
  <%
      }
  %>
</body>
</html>

Spusťte znovu aplikaci, ale tentokrát si prohlédněte stránku http://localhost:8080/ resp. http://localhost:8080/WebApplication1/.

JSTL a knihovny značek

Psaní scriptletů je nedoporučovaný způsob, protože takto vytvořené programy mají stejnou nectnost jako PHP programy - špatně se udržují.

Lepší je rozdělit aplikaci na část zpracovávající data, a část zobrazující data, tzv. MVC architektura. JSP se pak používají pouze pro zobrazení dat, a místo scriptletů se používá knihovna JSTL (JSP Standard Tag Library).

Podrobnosti viz Java Server Pages#Použití JSP.

JSTL - Praktická ukázka v IntelliJ IDEA

Do souboru pom.xml přidejte závislost na knihovně JSTL:

 
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.1.2</version>
        </dependency>
        <dependency>
            <groupId>taglibs</groupId>
            <artifactId>standard</artifactId>
            <version>1.1.2</version>
        </dependency>

Vytvořte v package cz.muni.fi.pv168.web nový servlet, např. UkazkaJSTL. Tento servlet připravuje do atributu requestu s klíčem jazyky mapu map, tj. Map<String, Map<String,Object>>, do které uloží data o jazycích připravených v JRE na lokalizace:

 
package cz.muni.fi.pv168.web;
 
import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
 
@WebServlet(urlPatterns = {"/UkazkaJSTL"})
public class UkazkaJSTL extends HttpServlet {
 
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
 
        Map<String, Map<String,Object>> m = new TreeMap<>();
 
        for(Locale l :Locale.getAvailableLocales()) {
            Map<String, Object> v = new HashMap<>();
            m.put(l.toString(), v);
            
            v.put("name", l.getDisplayName());
            v.put("origname", l.getDisplayName(l));
            v.put("loc", l);
        }
        request.setAttribute("jazyky", m);
 
        request.getRequestDispatcher("/stranka.jsp").forward(request, response);
    }
 
}

Vytvořte nový soubor src/main/webapp/stranka.jsp s obsahem:

 
<%@page contentType="text/html;charset=utf-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
    <body>
 
    <table border="1">
    <c:forEach items="${jazyky}" var="j">
        <tr>
            <td><c:out value="${j.key}"/></td>
            <td><c:out value="${j.value.name}"/></td>
            <td><c:out value="${j.value.origname}"/></td>
            <td><c:out value="${j.value.loc.ISO3Language}"/></td>
            
        </tr>    
    </c:forEach>
    </table>
 
    </body>
</html>

Spusťte aplikaci a zavolejte tento nový servlet na URL http://localhost:8080/UkazkaJSTL

JSTL - Praktická ukázka v NetBeans

Přidejte do projektu knihovnu JSTL 1.1. Klikněte pravým tlačítkem na Librarries - Add Library - JSTL 1.1.

Vytvořte v projektu nový servlet, např. UkazkaJSTL.

Upravte ho na stejný obsah jako u IntelliJ výše.

Vytvořte nový soubor stranka.jsp. Klikněte pravým tlačítkem na Web Pages - New - JSP, pozor jméno zadejte bez přípony .jsp !

Upravte ho na stejný obsah jako u IntelliJ výše.

Spusťte aplikaci a zavolejte tento nový servlet na URL http://localhost:8080/WebApplication1/UkazkaJSTL

Společné vysvětlení JSTL EL

V této ukázce servlet negeneruje žádný výstup, jen připraví data, uloží je do requestu a předá řízení na JSP stránku. JSP stránka si vyzvedne data z requestu, a pomocí značek z JSTL provede cyklus nad všemi položkami předané mapy (jsou typu java.util.Map.Entry). Jazyk EL s tečkovou notací je použit pro zpřístupnění vnořených objektů, tj. např.

${j.value.loc.ISO3Language}

je ve skutečnosti provedeno jako

 
 pageContext.findAttribute("j").getValue().get("loc").getISO3Language()

V rámci výuky jsou dostupné pro vyzkoušení i další aplikační servery Aplikacni servery

Úloha

Závislosti mezi projekty

Pro domácí úlohu č. 5 máte udělat jednoduché webové rozhraní pro svoji aplikaci, jejíž funkční část máte už hotovou z úlohy č. 4. Proto je výhodné vytvořit webové rozhraní jako nový projekt se závislostí na projektu obsahujícím úlohu č. 4.

V čistém Mavenu

Závislost jednoho projektu na druhém je třeba přidat v souboru pom.xml v části <dependencies> přidáním např.

 
        <dependency>
            <groupId>cz.muni.fi.pv168</groupId>
            <artifactId>books-jdbc</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

Pokud jsme autory obou projektů, je výhodné vytvořit tzv. parent projekt, který oba projekty obsahuje jako podadresáře. Ukázka je v gitové repository https://github.com/petradamek/PV168/tree/master/Books-webapp kde lze oba projekty zkompilovat, zabalit a nainstalovat do lokální repository v adresáři ~/.m2 pomocí příkazů:

git clone https://github.com/petradamek/PV168/
cd PV168/Books-webapp
mvn install

V IntelliJ IDEA

Protože používáme Maven, jde o vytvoření Mavenového projektu se závislostí na jiném projektu. To je velmi jednoduché, závislost popíšeme stejným způsobem jako závislost na libovolné jiné knihovně.

V projektu s webovou aplikací přidáme závislost:

 
        <dependency>
            <groupId>cz.muni.fi.pv168</groupId>
            <artifactId>books-jdbc</artifactId>
            <version>1.0</version>
        </dependency>

V čistém Mavenu je třeba projekt, na kterém jiný projekt závisí, přidat do tzv. repository, která se nalézá v adresáři ~/.m2/repository pomocí příkazu mvn install, nebo alternativně využít Maven vícemodulový projekt.

V IntelliJ IDEA je výhodnější využít vícemodulový projekt IDEA. Vyberte položku File - Import module, vyberte projekt, na kterém chcete vytvořit závislost, v následujícím okně zaškrtněte možnost Import Maven projects automatically a potvrďte.

Idea multimodule.png

V NetBeans

Vyklikáním přidejte do knihoven projektu závislost na jiném projektu.

Netbeans project dependency.png

Přístup k business logice

Inicializaci business logiky a přístupu k databázi schováme nejlépe do listeneru reagujícího na start aplikace, který nám instance manažerů uloží jako atributy ServletContextu:

 
package cz.muni.fi.pv168.web;
 
import cz.muni.fi.pv168.books.BookManager;
import cz.muni.fi.pv168.books.CustomerManager;
import cz.muni.fi.pv168.books.SpringConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
 
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
 
@WebListener
public class StartListener implements ServletContextListener {
 
    final static Logger log = LoggerFactory.getLogger(StartListener.class);
 
    @Override
    public void contextInitialized(ServletContextEvent ev) {
        log.info("aplikace inicializována");
        ServletContext servletContext = ev.getServletContext();
        ApplicationContext springContext = new AnnotationConfigApplicationContext(SpringConfig.class);
        servletContext.setAttribute("customerManager", springContext.getBean("customerManager", CustomerManager.class));
        servletContext.setAttribute("bookManager", springContext.getBean("bookManager", BookManager.class));
        log.info("vytvořeny manažery");
    }
 
    @Override
    public void contextDestroyed(ServletContextEvent ev) {
        log.info("aplikace končí");
    }
}

V servletech pak k nim přistoupíme pomocí vyzvednutí z atributů aplikace.

Formulář pro zadání

Do souboru list.jsp dejte obsah obdobný tomuto:

 
<%@page contentType="text/html;charset=utf-8" pageEncoding="UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<body>
 
<table border="1">
    <thead>
    <tr>
        <th>název</th>
        <th>autor</th>
    </tr>
    </thead>
    <c:forEach items="${books}" var="book">
        <tr>
            <td><c:out value="${book.name}"/></td>
            <td><c:out value="${book.author}"/></td>
            <td><form method="post" action="${pageContext.request.contextPath}/books/delete?id=${book.id}"
                      style="margin-bottom: 0;"><input type="submit" value="Smazat"></form></td>
        </tr>
    </c:forEach>
</table>
 
<h2>Zadejte knihu</h2>
<c:if test="${not empty chyba}">
    <div style="border: solid 1px red; background-color: yellow; padding: 10px">
        <c:out value="${chyba}"/>
    </div>
</c:if>
<form action="${pageContext.request.contextPath}/books/add" method="post">
    <table>
        <tr>
            <th>název knihy:</th>
            <td><input type="text" name="name" value="<c:out value='${param.name}'/>"/></td>
        </tr>
        <tr>
            <th>autor:</th>
            <td><input type="text" name="author" value="<c:out value='${param.author}'/>"/></td>
        </tr>
    </table>
    <input type="Submit" value="Zadat" />
</form>
</body>
</html>

K této JSP stránce je potřeba mít servlet, který bude obsluhovat požadavky, při požadavku metodou GET zobrazí stránku se seznamem, a při požadavku metodou POST se podle URL rozhodne, zda má smazat položku podle parametru id, nebo přidat novou položku podle parametrů předaných z formuláře:

 
package cz.muni.fi.pv168.web;
 
import cz.muni.fi.pv168.books.Book;
import cz.muni.fi.pv168.books.BookException;
import cz.muni.fi.pv168.books.BookManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
 
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
 
/**
 * Servlet for managing books.
 */
@WebServlet(BooksServlet.URL_MAPPING + "/*")
public class BooksServlet extends HttpServlet {
 
    private static final String LIST_JSP = "/list.jsp";
    public static final String URL_MAPPING = "/books";
 
    private final static Logger log = LoggerFactory.getLogger(BooksServlet.class);
 
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        showBooksList(request, response);
    }
 
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //aby fungovala čestina z formuláře
        request.setCharacterEncoding("utf-8");
        //akce podle přípony v URL
        String action = request.getPathInfo();
        switch (action) {
            case "/add":
                //načtení POST parametrů z formuláře
                String name = request.getParameter("name");
                String author = request.getParameter("author");
                //kontrola vyplnění hodnot
                if (name == null || name.length() == 0 || author == null || author.length() == 0) {
                    request.setAttribute("chyba", "Je nutné vyplnit všechny hodnoty !");
                    showBooksList(request, response);
                    return;
                }
                //zpracování dat - vytvoření záznamu v databázi
                try {
                    Book book = new Book(null, name, author);
                    getBookManager().createBook(book);
                    log.debug("created {}",book);
                    //redirect-after-POST je ochrana před vícenásobným odesláním formuláře
                    response.sendRedirect(request.getContextPath()+URL_MAPPING);
                    return;
                } catch (BookException e) {
                    log.error("Cannot add book", e);
                    response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
                    return;
                }
            case "/delete":
                try {
                    Long id = Long.valueOf(request.getParameter("id"));
                    getBookManager().deleteBook(id);
                    log.debug("deleted book {}",id);
                    response.sendRedirect(request.getContextPath()+URL_MAPPING);
                    return;
                } catch (BookException e) {
                    log.error("Cannot delete book", e);
                    response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
                    return;
                }
            case "/update":
                //TODO
                return;
            default:
                log.error("Unknown action " + action);
                response.sendError(HttpServletResponse.SC_NOT_FOUND, "Unknown action " + action);
        }
    }
 
    /**
     * Gets BookManager from ServletContext, where it was stored by {@link StartListener}.
     *
     * @return BookManager instance
     */
    private BookManager getBookManager() {
        return (BookManager) getServletContext().getAttribute("bookManager");
    }
 
    /**
     * Stores the list of books to request attribute "books" and forwards to the JSP to display it.
     */
    private void showBooksList(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        try {
            request.setAttribute("books", getBookManager().getAllBooks());
            request.getRequestDispatcher(LIST_JSP).forward(request, response);
        } catch (BookException e) {
            log.error("Cannot show books", e);
            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
        }
    }
 
}

u webových aplikací obecně platí, že požadavky HTTP metodou GET by měly být tzv idempotentní - neměnící stav na serveru. Naopak požadavky měnící stav na serveru musí být prováděny metodou POST. Proto je smazání a vytvoření nového záznamu v servletu umístěno v (Java) metodě doPost(), zatímco zobrazení stránky je v metodě doGet().