JavaServlets: Porovnání verzí
m (Zamyká „JavaServlets“: spam [edit=autoconfirmed:move=autoconfirmed]) |
(→Servlet API) |
||
(Není zobrazeno 25 mezilehlých verzí od 5 dalších uživatelů.) | |||
Řádka 3: | Řádka 3: | ||
[[Kategorie:PA165]] | [[Kategorie:PA165]] | ||
[[Kategorie:Webové aplikace]] | [[Kategorie:Webové aplikace]] | ||
+ | |||
+ | == Managed environment == | ||
+ | |||
+ | U servletů se poprvé setkáváme s pojmem '''managed environment'''. Servlety nejsou spustitelné aplikace, jak | ||
+ | jsme byli dosud zvyklí, ale "žijí" uvnitř určitého prostředí, zvaného '''servlet container'''. | ||
+ | |||
+ | Servlet container vytváří instance servletů, při příchodu HTTP požadavků zpracuje příchozí data a volá metody servletů, vygenerovanou | ||
+ | odpověd zasílá prohlížeči, a při ukončení aplikace servlety ruší. | ||
+ | |||
+ | Je definováno rozhraní mezi servlet containerem a webovou aplikací tvořenou servlety, tzv. '''Servlet API''', | ||
+ | které má různé verze, v současnosti je nejnovější verze 3.1. | ||
+ | |||
+ | Existují různé implementace servlet containerů, např. Apache Tomcat, Jetty, JRun. Dále pak | ||
+ | každý server odpovídající specifikaci Java Enterprise Edition musí obsahovat servlet container, | ||
+ | např. BEA WebLogic, Oracle Application Server, IBM WebSphere a další. | ||
+ | |||
+ | (podíl aplikačních serverů, zdroj http://jaxenter.com/java-2-111936.html) | ||
+ | |||
+ | [[Image:Java app servers.png|800px]] | ||
+ | |||
+ | Díky '''jednotnému API''' je možné psát aplikace nezávislé na konkrétním servlet containeru, | ||
+ | a tutéž aplikaci používat v servlet containerech různých výrobců. | ||
== Definice servletu == | == Definice servletu == | ||
Řádka 41: | Řádka 63: | ||
p3=Lojza&p4=Franta | p3=Lojza&p4=Franta | ||
− | První řádek specifikuje tzv. '''metodu''', která může být GET,POST,PUT,HEAD,DELETE,OPTIONS,TRACE, dále části <code>path</code> a <code>queryString</code> z URL, a verzi protokolu HTTP (1.0 | + | První řádek specifikuje tzv. '''metodu''', která může být GET,POST,PUT,HEAD,DELETE,OPTIONS,TRACE, dále části <code>path</code> a <code>queryString</code> z URL, a verzi protokolu HTTP (1.0, 1.1, 2). |
Další řádky až po prázdný řádek jsou tzv. hlavičky, obdobné MIME hlavičkám používaným v e-mailech. Prázdný řádek označuje konec hlaviček. U metody POST následuje za ním ještě datový obsah, jehož typ je určen hlavičkou <code>Content-type</code>. Ve výše uvedeném příkladu je to typ pro předávání dat z HTML formulářů, kdy jednotlivé položky formuláře jsou odděleny znakem ampersand, a mají tvar dvojice jméno=hodnota. | Další řádky až po prázdný řádek jsou tzv. hlavičky, obdobné MIME hlavičkám používaným v e-mailech. Prázdný řádek označuje konec hlaviček. U metody POST následuje za ním ještě datový obsah, jehož typ je určen hlavičkou <code>Content-type</code>. Ve výše uvedeném příkladu je to typ pro předávání dat z HTML formulářů, kdy jednotlivé položky formuláře jsou odděleny znakem ampersand, a mají tvar dvojice jméno=hodnota. | ||
Řádka 54: | Řádka 76: | ||
... | ... | ||
− | První řádek odpovědi určuje kód výsledku (200 OK, 301 Moved Permanently, ...), následují hlavičky, z nichž <code>Content-type</code> určuje typ obsahu za prázdným řádkem. | + | První řádek odpovědi určuje [http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html kód výsledku] (200 OK, 301 Moved Permanently, ...), následují hlavičky, z nichž <code>Content-type</code> určuje typ obsahu za prázdným řádkem. |
HTTP protokol je nezávislý na implementaci, tedy zda na straně serveru používáme Java Servlety, CGI programy, PHP, Perl, či něco jiného. | HTTP protokol je nezávislý na implementaci, tedy zda na straně serveru používáme Java Servlety, CGI programy, PHP, Perl, či něco jiného. | ||
+ | |||
+ | Verze HTTP 2 přináší multiplexing více HTTP requestů přes jediné TCP spojení, oproti HTTP 1.1/1.0 kde prohlížeče typicky otevírají 6 současných TCP spojení na jeden server. | ||
== Obsluha HTTP pomocí servletů == | == Obsluha HTTP pomocí servletů == | ||
Řádka 105: | Řádka 129: | ||
Objekt <code>[http://java.sun.com/javaee/5/docs/api/javax/servlet/http/HttpServletResponse.html HttpServletResponse]</code> obsahuje všechny nástroje pro vygenerování libovolné HTTP odpovědi, je možné měnit kód výsledku pomocí metody <code>setStatus()</code>, měnit HTTP hlavičky pomocí <code>addHeader()</code>, přidávat HTTP cookies pomocí <code>addCookie()</code> nebo přesměrovat prohlížeč na jinou stránku pomocí <code>sendRedirect()</code>. | Objekt <code>[http://java.sun.com/javaee/5/docs/api/javax/servlet/http/HttpServletResponse.html HttpServletResponse]</code> obsahuje všechny nástroje pro vygenerování libovolné HTTP odpovědi, je možné měnit kód výsledku pomocí metody <code>setStatus()</code>, měnit HTTP hlavičky pomocí <code>addHeader()</code>, přidávat HTTP cookies pomocí <code>addCookie()</code> nebo přesměrovat prohlížeč na jinou stránku pomocí <code>sendRedirect()</code>. | ||
+ | |||
+ | (Ukázka možností servletu je na stránce [[PA165/Cvičení webové aplikace 1 - Servlet API 3.0#Servlet|PA165/Cvičení webové aplikace 1 - Servlet API 3.0]].) | ||
=== Zpracování HTTP požadavku === | === Zpracování HTTP požadavku === | ||
Řádka 193: | Řádka 219: | ||
Pro korektní '''příjem parametrů''' je '''nutné''' před prvním zavoláním <code>getParameter()</code> '''zavolat''' <code>request.setCharacterEncoding()</code>, | Pro korektní '''příjem parametrů''' je '''nutné''' před prvním zavoláním <code>getParameter()</code> '''zavolat''' <code>request.setCharacterEncoding()</code>, | ||
pak budou parametry překódovány správně. | pak budou parametry překódovány správně. | ||
+ | |||
+ | Podrobnější rozbor problematiky kódování je v hesle [[I18n - Internacionalizace]]. | ||
== Okolí servletu == | == Okolí servletu == | ||
Řádka 203: | Řádka 231: | ||
Servlet nemůže existovat sám o sobě, potřebuje jisté okolí, které převádí příchozí HTTP požadavky na volání servletu. Proto servlety existují uvnitř tzv. '''servletového kontejneru'''. Existují samostatné implementace servletových kontejnerů, např. TomCat, Jetty, nebo může být servletový kontejner součástí většího Java EE aplikačního serveru, např. Glassfish, JBoss, IBM WebSphere atd. | Servlet nemůže existovat sám o sobě, potřebuje jisté okolí, které převádí příchozí HTTP požadavky na volání servletu. Proto servlety existují uvnitř tzv. '''servletového kontejneru'''. Existují samostatné implementace servletových kontejnerů, např. TomCat, Jetty, nebo může být servletový kontejner součástí většího Java EE aplikačního serveru, např. Glassfish, JBoss, IBM WebSphere atd. | ||
− | Rozhraní mezi servletem a servletovým kontejnerem je specifikováno v tzv. '''Servlet API''', které je dnes součástí Java Enterprise Edition. | + | Rozhraní mezi servletem a servletovým kontejnerem je specifikováno v tzv. '''Servlet API''', které je dnes součástí [[Java EE|Java Enterprise Edition]]. V současnosti používané verze této specifikace jsou 2.5 a 3.0, nově přibyla 3.1 . |
− | V současnosti používané verze této specifikace jsou 2. | + | |
Řádka 210: | Řádka 237: | ||
! Servlet !! doba vzniku !! platforma !! implementace !! novinky | ! Servlet !! doba vzniku !! platforma !! implementace !! novinky | ||
|- | |- | ||
− | | Servlet 2.5 || [http://www.javaworld.com/javaworld/jw-01-2006/jw-0102-servlet.html září 2005] || JavaEE 5 , JavaSE 5.0 || TomCat 6.x, Jetty 6, Glassfish || | + | | Servlet 4.0 || [https://jcp.org/en/jsr/detail?id=369 září 2017] || JavaEE 8 , JavaSE 8.0 || TomCat 9.x || HTTP/2 |
+ | |- | ||
+ | | Servlet 3.1 || [http://jcp.org/en/jsr/detail?id=340 květen 2013] || JavaEE 7 , JavaSE 7.0 || TomCat 8.x, Jetty 9.1, Glassfish 4 || Non-blocking I/O, HTTP protocol upgrade mechanism | ||
+ | |- | ||
+ | | Servlet 3.0 || [http://www.javaworld.com/javaworld/jw-02-2009/jw-02-servlet3.html prosinec 2009] || JavaEE 6 , JavaSE 6.0 || TomCat 7.x, Jetty 8, Glassfish 3 || anotace @WebServlet, @WebFilter; asynchronní zpracování; podpora file upload | ||
+ | |- | ||
+ | | Servlet 2.5 || [http://www.javaworld.com/javaworld/jw-01-2006/jw-0102-servlet.html září 2005] || JavaEE 5 , JavaSE 5.0 || TomCat 6.x, Jetty 6, Glassfish || přidává anotace @Resource | ||
|- | |- | ||
| Servlet 2.4 || [http://www.javaworld.com/jw-03-2003/jw-0328-servlet.html listopad 2003]|| J2EE 1.4, J2SE 1.3|| TomCat 5.x || web.xml používá XML Schema | | Servlet 2.4 || [http://www.javaworld.com/jw-03-2003/jw-0328-servlet.html listopad 2003]|| J2EE 1.4, J2SE 1.3|| TomCat 5.x || web.xml používá XML Schema | ||
Řádka 234: | Řádka 267: | ||
/*/*.jsp ... JSP stránky | /*/*.jsp ... JSP stránky | ||
/*/*.* ... HTML, CSS, obrázky atd. | /*/*.* ... HTML, CSS, obrázky atd. | ||
+ | |||
+ | ==== Mapování servletů na URL ==== | ||
Název webové aplikace (název WAR souboru a adresáře z něho vzniklého) je obvykle použit jako začátek cesty v URL odpovídajících této webové aplikaci. | Název webové aplikace (název WAR souboru a adresáře z něho vzniklého) je obvykle použit jako začátek cesty v URL odpovídajících této webové aplikaci. | ||
+ | |||
+ | ===== Mapování ve web.xml ===== | ||
Soubor <code>/WEB-INF/web.xml</code> obsahuje definice mapování URL na servlety, plus další konfigurační údaje. Mapování URL na servlety se provádí | Soubor <code>/WEB-INF/web.xml</code> obsahuje definice mapování URL na servlety, plus další konfigurační údaje. Mapování URL na servlety se provádí | ||
buď podle začátku cesty v URL, nebo podle přípony. Následující příklad určuje, že třída <code>MujServlet</code> bude zavolána | buď podle začátku cesty v URL, nebo podle přípony. Následující příklad určuje, že třída <code>MujServlet</code> bude zavolána | ||
Řádka 257: | Řádka 294: | ||
by byl vyvolán pro URL s cestou začínající <code>/mojeapp/mujservlet/</code>. | by byl vyvolán pro URL s cestou začínající <code>/mojeapp/mujservlet/</code>. | ||
+ | Jeden servlet (Java třída) může mít více instancí. Jedna instance může být mapována na různá URL. | ||
+ | |||
+ | ===== Anotace @WebServlet a web-fragment.xml ===== | ||
+ | |||
+ | Od verze Servlet API 3.0 je možné místo do ''deployment descriptoru'' WEB-INF/web.xml uložit stejnou informaci na dvě další místa, do [[Java:Zákoutí_jazyka#Anotace|anotací]] | ||
+ | <java> | ||
+ | @WebServlet(name = "MujServlet",urlPatterns = {"/muj/*","*.muj"}) | ||
+ | public class MujServlet extends HttpServlet { | ||
+ | //... | ||
+ | } | ||
+ | </java> | ||
+ | |||
+ | nebo do souboru uvnitř některého JAR balíku, tj. WEB-INF/lib/*.jar/META-INF/web-fragment.xml, viz [http://blogs.oracle.com/swchan/entry/servlet_3_0_web_fragment Servlet 3.0 web-fragment.xml]. | ||
+ | |||
+ | ==== Filtry ==== | ||
Kromě servletů může webová aplikace obsahovat ješte servletové filtry, což jsou Java třídy implementující interface [http://java.sun.com/javaee/5/docs/api/javax/servlet/Filter.html javax.servlet.Filter], umožňující ovlivnit zpracování HTTP volání | Kromě servletů může webová aplikace obsahovat ješte servletové filtry, což jsou Java třídy implementující interface [http://java.sun.com/javaee/5/docs/api/javax/servlet/Filter.html javax.servlet.Filter], umožňující ovlivnit zpracování HTTP volání | ||
dřív, než se dostane k servletům. Do filtrů je vhodné například umístit nastavení kódování českých znaků v požadavku. | dřív, než se dostane k servletům. Do filtrů je vhodné například umístit nastavení kódování českých znaků v požadavku. | ||
− | + | ==== ServletContext a inicializační parametry ==== | |
Webová aplikace se v terminologii Servlet API nazývá '''kontext servletu''', a každý servlet má možnost o ní získat informace pomocí volání <code>getServletContext()</code>, které vrací objekt [http://java.sun.com/javaee/5/docs/api/javax/servlet/ServletContext.html ServletContext]. | Webová aplikace se v terminologii Servlet API nazývá '''kontext servletu''', a každý servlet má možnost o ní získat informace pomocí volání <code>getServletContext()</code>, které vrací objekt [http://java.sun.com/javaee/5/docs/api/javax/servlet/ServletContext.html ServletContext]. | ||
Řádka 271: | Řádka 323: | ||
nebo jiným mechanismem závislým na konkrétní implementaci servletového kontejneru. V každém případě umožňují nastavovat proměnlivé údaje | nebo jiným mechanismem závislým na konkrétní implementaci servletového kontejneru. V každém případě umožňují nastavovat proměnlivé údaje | ||
beze změn programovacího kódu a rekompilace servletů. | beze změn programovacího kódu a rekompilace servletů. | ||
− | |||
== HttpSession == | == HttpSession == | ||
Řádka 292: | Řádka 343: | ||
== Spolupráce mezi servlety == | == Spolupráce mezi servlety == | ||
− | Příchozí HTTP požadavek může obsloužit jeden servlet sám, nebo je možné použít složitějšího postupu. Zejména při použití sofistikovaných rámců pro tvorbu webových aplikací (např. Apache Struts), je jeden HTTP požadavek zpracováván celou řadou servletů, které si postupně předávají řízení. | + | Příchozí HTTP požadavek může obsloužit jeden servlet sám, nebo je možné použít složitějšího postupu. Zejména při použití sofistikovaných rámců pro tvorbu webových aplikací (např. [[Apache Struts]]), je jeden HTTP požadavek zpracováván celou řadou servletů, které si postupně předávají řízení. |
Například jeden servlet zpracuje příchozí informace, podle výsledku vybere servlet generující HMTL stránku a předá mu řízení. Ten | Například jeden servlet zpracuje příchozí informace, podle výsledku vybere servlet generující HMTL stránku a předá mu řízení. Ten | ||
pro generování různých částí stránky (hlavička, navigační menu, patička, atd.) volá různé další servlety. | pro generování různých částí stránky (hlavička, navigační menu, patička, atd.) volá různé další servlety. | ||
Řádka 298: | Řádka 349: | ||
Předávání řízení mezi servlety se provádí pomocí třídy <code>[http://java.sun.com/javaee/5/docs/api/javax/servlet/RequestDispatcher.html javax.servlet.RequestDispatcher]</code>, která se získá voláním <code>getRequestDispatcher()</code> na <code>ServletContext</code> nebo <code>HttpServletRequest</code>. Třída <code>RequestDispatcher</code> umožňuje buď předat řízení úplně pomocí <code>forward()</code>, | Předávání řízení mezi servlety se provádí pomocí třídy <code>[http://java.sun.com/javaee/5/docs/api/javax/servlet/RequestDispatcher.html javax.servlet.RequestDispatcher]</code>, která se získá voláním <code>getRequestDispatcher()</code> na <code>ServletContext</code> nebo <code>HttpServletRequest</code>. Třída <code>RequestDispatcher</code> umožňuje buď předat řízení úplně pomocí <code>forward()</code>, | ||
nebo jen dočasně pomocí <code>include()</code>. | nebo jen dočasně pomocí <code>include()</code>. | ||
+ | |||
+ | Typicky se toto předání provádí mezi servletem, který zpracoval data, a JSP stránkou (což je druh servletu), která data zobrazí, pomocí kódu: | ||
+ | <java> | ||
+ | //předání dat do dalšího servletu | ||
+ | request.setAttribute("seznam", Arrays.asList("mléko", "rohlíky", "salám")); | ||
+ | //předání požadavku jinému servletu | ||
+ | request.getRequestDispatcher("/stranka.jsp").forward(request, response); | ||
+ | //konec zpracování | ||
+ | return; | ||
+ | </java> | ||
=== Rozsahy platnosti atributů === | === Rozsahy platnosti atributů === | ||
Řádka 319: | Řádka 380: | ||
Session znamená, že všechny HTTP požadavky | Session znamená, že všechny HTTP požadavky | ||
− | přicházející od jednoho webové prohlížeče sdílí instanci | + | přicházející od jednoho webové prohlížeče sdílí instanci {{JavaEEClass|class=HttpSession|package=javax/servlet/http}}, |
+ | která umožňuje identifikovat jednoho uživatele. Různí uživatelé mají různé session. | ||
Stejnojmené atributy tedy mohou mít při nastavení do session různé hodnoty pro různé uživatele. | Stejnojmené atributy tedy mohou mít při nastavení do session různé hodnoty pro různé uživatele. | ||
Řádka 327: | Řádka 389: | ||
ze atributy v tomto rozsahu platnosti jsou vidět jen v rámci jedné JSP stránky, nepřenáší se do jiných stránek zavolaných pomocí <code><jsp:include></code> nebo <code><jsp:forward></code>. Používají se pro předávání informací mezi JSP tagy. | ze atributy v tomto rozsahu platnosti jsou vidět jen v rámci jedné JSP stránky, nepřenáší se do jiných stránek zavolaných pomocí <code><jsp:include></code> nebo <code><jsp:forward></code>. Používají se pro předávání informací mezi JSP tagy. | ||
− | + | == Listenery == | |
Webová aplikace může ve web.xml definovat, že určité třídy budou dostávat upozornění na určité události. | Webová aplikace může ve web.xml definovat, že určité třídy budou dostávat upozornění na určité události. | ||
Řádka 338: | Řádka 400: | ||
* <code>HttpSessionActivationListener</code> aktivace a pasivace session při přenosu mezi VM | * <code>HttpSessionActivationListener</code> aktivace a pasivace session při přenosu mezi VM | ||
* <code>HttpSessionBindingListener</code> objekt se dozví o svém přidání do session | * <code>HttpSessionBindingListener</code> objekt se dozví o svém přidání do session | ||
+ | |||
+ | == Upload souborů == | ||
+ | |||
+ | Do verze Servlet API 2.5 je nutné řešit upload souborů pomocí externí knihovny, která dokáže zpracovat | ||
+ | speciální typ HTTP requestu se souborem. | ||
+ | |||
+ | Od verze 3.0 je podpora upload souborů přímo v Servlet API. Formulář pro odeslání musí mít specifikovaný typ | ||
+ | enctype="multipart/form-data a pro každý soubor input typu file: | ||
+ | |||
+ | <xml> | ||
+ | <form enctype="multipart/form-data" action="/nahrani" method="POST"> | ||
+ | <input name="f" type="file"/><br/> | ||
+ | <input type="submit" value="Upload"/> | ||
+ | </form> | ||
+ | </xml> | ||
+ | |||
+ | Servlet pak musí mít anotaci @MultipartConfig a soubory získá přes metodu <code>getParts()</code>: | ||
+ | |||
+ | <java> | ||
+ | import javax.servlet.ServletException; | ||
+ | import javax.servlet.annotation.MultipartConfig; | ||
+ | import javax.servlet.annotation.WebServlet; | ||
+ | import javax.servlet.http.HttpServlet; | ||
+ | import javax.servlet.http.HttpServletRequest; | ||
+ | import javax.servlet.http.HttpServletResponse; | ||
+ | import javax.servlet.http.Part; | ||
+ | import java.io.IOException; | ||
+ | import java.io.PrintWriter; | ||
+ | import java.util.concurrent.atomic.AtomicInteger; | ||
+ | import java.util.regex.Matcher; | ||
+ | import java.util.regex.Pattern; | ||
+ | |||
+ | @WebServlet(name = "UploadServlet", urlPatterns = {"/nahrani"}) | ||
+ | @MultipartConfig(location = "/tmp", fileSizeThreshold = 1000000, maxFileSize = 5000000) | ||
+ | public class UploadServlet extends HttpServlet { | ||
+ | |||
+ | static AtomicInteger counter = new AtomicInteger(0); | ||
+ | |||
+ | @Override | ||
+ | protected void doPost(HttpServletRequest req, HttpServletResponse resp) | ||
+ | throws ServletException, IOException { | ||
+ | resp.setContentType("text/plain; charset=utf-8"); | ||
+ | PrintWriter out = resp.getWriter(); | ||
+ | for (Part part : req.getParts()) { | ||
+ | out.println(); | ||
+ | out.println("parameter name: " + part.getName()); | ||
+ | out.println("file size: " + part.getSize()); | ||
+ | out.println("content-type: " + part.getContentType()); | ||
+ | String fileName = "soubor"; | ||
+ | String dis = part.getHeader("content-disposition"); | ||
+ | if (part.getContentType() != null && dis != null) { | ||
+ | Matcher m = Pattern.compile("filename=\"([^\"]*)\"").matcher(dis); | ||
+ | if (m.find()) { | ||
+ | fileName = m.group(1); | ||
+ | out.print("file name=" + fileName); | ||
+ | } | ||
+ | } | ||
+ | part.write(counter.incrementAndGet()+"_"+fileName); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </java> | ||
+ | |||
+ | Z bezpečnostních důvodů je třeba omezit maximální velikost přijatého souboru, a uložit soubor pod nějakým jedinečným jménem. | ||
+ | |||
+ | == Asynchronní zpracování == | ||
+ | |||
+ | Od Servlet API 3.0 je kvůli škálovatelnosti možné zpracovávat requesty a generovat response v jiném vlákně. Smysl to má například, pokud pro vygenerování odpovědi je nutné kontaktovat vnější službu s delší latencí. | ||
+ | |||
+ | <java> | ||
+ | @WebServlet(urlPatterns = {"/myasync"}, asyncSupported = true) | ||
+ | public class MyAsyncServlet extends HttpServlet { | ||
+ | |||
+ | final static Logger log = LoggerFactory.getLogger(MyAsyncServlet.class); | ||
+ | |||
+ | private ExecutorService executorService; | ||
+ | private BlockingQueue<AsyncContext> queue; | ||
+ | |||
+ | @Override | ||
+ | public void init() throws ServletException { | ||
+ | //create request queue | ||
+ | queue = new LinkedBlockingQueue<>(); | ||
+ | //create async worker threads | ||
+ | int poolSize = Runtime.getRuntime().availableProcessors(); | ||
+ | executorService = Executors.newFixedThreadPool(poolSize); | ||
+ | for (int i = 0; i < poolSize; i++) executorService.execute(new MyWorker()); | ||
+ | } | ||
+ | |||
+ | @Override | ||
+ | public void destroy() { | ||
+ | log.debug("shutdown executor pool"); | ||
+ | executorService.shutdownNow(); | ||
+ | } | ||
+ | |||
+ | @Override | ||
+ | protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { | ||
+ | log.debug("doGet()"); | ||
+ | AsyncContext asyncContext = request.startAsync(); | ||
+ | queue.add(asyncContext); | ||
+ | } | ||
+ | |||
+ | private class MyWorker implements Runnable { | ||
+ | @Override | ||
+ | public void run() { | ||
+ | try { | ||
+ | AsyncContext asyncContext = queue.take(); | ||
+ | log.debug("got work ..."); | ||
+ | //pretend to work for long | ||
+ | Thread.sleep(5000); | ||
+ | //write response | ||
+ | ServletResponse response = asyncContext.getResponse(); | ||
+ | ServletOutputStream out = response.getOutputStream(); | ||
+ | response.setContentType("text/html;charset=utf-8"); | ||
+ | for (int i = 0; i < 12000; i++) out.println("<br>" + i + " hello world"); | ||
+ | log.debug("writing completed"); | ||
+ | //mark request as completed | ||
+ | asyncContext.complete(); | ||
+ | } catch (InterruptedException e) { | ||
+ | log.trace("e", e); | ||
+ | } catch (IOException e) { | ||
+ | log.error("e", e); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </java> | ||
+ | |||
+ | Od Servlet API 3.1 jsou navíc k dispozici tzv. neblokující I/O ve formě tříd [https://docs.oracle.com/javaee/7/api/index.html?javax/servlet/ReadListener.html javax.servlet.ReadListener] a javax.servlet.WriteListener. | ||
== Shrnutí == | == Shrnutí == | ||
Tato stránka je součástí souboru hesel o [[:Kategorie:Webové aplikace|webových aplikacích]]. Popisuje Java servlety, což jsou nástroje pro obsluhu HTTP protokolu na straně serveru. Pracují na velice nízké úrovni, takže zatímco umožňují plnou flexibilitu při zpracování HTTP, jsou nepohodlné na programování. Proto je pro tvorbu webových aplikací vhodné použít jejich nadstavby, | Tato stránka je součástí souboru hesel o [[:Kategorie:Webové aplikace|webových aplikacích]]. Popisuje Java servlety, což jsou nástroje pro obsluhu HTTP protokolu na straně serveru. Pracují na velice nízké úrovni, takže zatímco umožňují plnou flexibilitu při zpracování HTTP, jsou nepohodlné na programování. Proto je pro tvorbu webových aplikací vhodné použít jejich nadstavby, | ||
− | zejména [[Java Server Pages]] a rámce pro tvorbu aplikací, např. [[Apache Struts]]. Specifikace Servlet API jsou od jisté verze přímo svázány se specifikacemi Java Server Pages, proto pokračujte tímto tématem. | + | zejména [[Java Server Pages]] a rámce pro tvorbu aplikací, např. [[Apache Struts]]. Specifikace Servlet API jsou od jisté verze přímo svázány se specifikacemi [[JSP|Java Server Pages]], proto pokračujte tímto tématem. |
Aktuální verze z 3. 4. 2018, 13:42
Obsah
Managed environment
U servletů se poprvé setkáváme s pojmem managed environment. Servlety nejsou spustitelné aplikace, jak jsme byli dosud zvyklí, ale "žijí" uvnitř určitého prostředí, zvaného servlet container.
Servlet container vytváří instance servletů, při příchodu HTTP požadavků zpracuje příchozí data a volá metody servletů, vygenerovanou odpověd zasílá prohlížeči, a při ukončení aplikace servlety ruší.
Je definováno rozhraní mezi servlet containerem a webovou aplikací tvořenou servlety, tzv. Servlet API, které má různé verze, v současnosti je nejnovější verze 3.1.
Existují různé implementace servlet containerů, např. Apache Tomcat, Jetty, JRun. Dále pak každý server odpovídající specifikaci Java Enterprise Edition musí obsahovat servlet container, např. BEA WebLogic, Oracle Application Server, IBM WebSphere a další.
(podíl aplikačních serverů, zdroj http://jaxenter.com/java-2-111936.html)
Díky jednotnému API je možné psát aplikace nezávislé na konkrétním servlet containeru, a tutéž aplikaci používat v servlet containerech různých výrobců.
Definice servletu
Servlety jsou nástroj pro obsluhu protokolu HTTP na straně serveru. Přesně řečeno, jsou serverovým nástrojem pro obsluhu všech protokolů, které fungují způsobem požadavek-odpověď, nicméně už dlouhá léta je jediným podporovaným protokolem HTTP.
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
.
Servlety jsou základním kamenem, na kterém jsou vystavěny další vrstvy webových aplikací na platformě Java. Servlety jsou nízkoúrovňovým nástrojem pro obsluhu HTTP, takže na jednu stranu je možné s jejich pomocí obsloužit opravdu libovolný HTTP požadavek a vygenerovat libovolnou odpověď, na druhou stranu jsou pro rutinní generování HTML stránek příliš nepohodlné, a proto existují jejich nadstavby, zejména Java Server Pages.
Protokol HTTP
Pro pochopení servletů je důležité chápat, jak funguje protokol HTTP. Protokol HTTP (Hyper Text Transfer Protocol) je základním protokolem pro transport souborů a dat na webu. Jde o jednoduchý textový protokol na aplikační vrstvě, fungující nad TCP/IP. Jeho použití má dvě fáze, požadavek klienta na server, bezprostředně následovaný odpovědí serveru v rámci stejného TCP spojení. Požadavek žádá o webový zdroj, jenž je adresován URL (Uniform Resource Identifier), které má pro protokol HTTP obecnou podobu:
http://user:password@machine:port/path?queryString#fragment
- http
- označuje protokol HTTP, případně může být tvaru
https
, pokud je přenos šifrován pomocí SSL - user:password
- jméno a heslo uživatele, nedoporučuje se jej ale uvádět do URL kvůli bezpečnosti
- port
- číslo TCP portu, pokud není uvedeno, je to 80 pro http a 443 pro https
- path
- cesta nějakou hierarchií, např. souborovým systémem
- queryString
- doplňkové informace, typicky obsah polí z HTML formuláře
- fragment
- místo na stránce, je interpretován prohlížečem
Požadavek protokolem HTTP vypadá např. takto:
POST /mujservlet?p1=Pepa&p2=Mirek HTTP/1.1 Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5 Accept-language: cs,en-us;q=0.7,en;q=0.3 Connection: keep-alive Host: www.cesnet.cz Keep-Alive: 300 User-agent: Mozilla/5.0 (X11; U; Linux i686; cs-CZ; rv:1.8.1.6) Gecko/20070730 Firefox/2.0.0.6 Cookie: JSESSIONID=8823BD0993467256538E07E3FE10B883; style=meta; Content-type: application/x-www-form-url-encoded Content-length: 18
p3=Lojza&p4=Franta
První řádek specifikuje tzv. metodu, která může být GET,POST,PUT,HEAD,DELETE,OPTIONS,TRACE, dále části path
a queryString
z URL, a verzi protokolu HTTP (1.0, 1.1, 2).
Další řádky až po prázdný řádek jsou tzv. hlavičky, obdobné MIME hlavičkám používaným v e-mailech. Prázdný řádek označuje konec hlaviček. U metody POST následuje za ním ještě datový obsah, jehož typ je určen hlavičkou Content-type
. Ve výše uvedeném příkladu je to typ pro předávání dat z HTML formulářů, kdy jednotlivé položky formuláře jsou odděleny znakem ampersand, a mají tvar dvojice jméno=hodnota.
Odpověď serveru na HTTP požadavek vypadá obdobně:
HTTP/1.1 200 OK Date: Tue, 09 Oct 2007 10:00:12 GMT Server: Apache/2.2.6 Connection: close Content-Type: text/html; charset=utf-8
<html> ...
První řádek odpovědi určuje kód výsledku (200 OK, 301 Moved Permanently, ...), následují hlavičky, z nichž Content-type
určuje typ obsahu za prázdným řádkem.
HTTP protokol je nezávislý na implementaci, tedy zda na straně serveru používáme Java Servlety, CGI programy, PHP, Perl, či něco jiného.
Verze HTTP 2 přináší multiplexing více HTTP requestů přes jediné TCP spojení, oproti HTTP 1.1/1.0 kde prohlížeče typicky otevírají 6 současných TCP spojení na jeden server.
Obsluha HTTP pomocí servletů
HTTP servlety implementují metodu service()
. HTTP volání jsou reprezentovány vlákny, které volají metodu service()
, a předávají jí dvojici parametrů reprezentujících HTTP požadavek a HTTP odpověď. Vlastní obsluha volání tak může být přímo v této metodě. Její standardní implementace však podle HTTP metody volá některou z metod doGet()
, doPost()
, doPut()
, atd., do kterých je možné umístit obsluhu konkrétních HTTP metod.
Generování HTTP odpovědi
Minimalistický servlet, který generuje vždy stejný HTML text, je zde:
import javax.servlet.http.*; import javax.servlet.*; import java.io.*; public class Ukazka extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html; charset=utf-8"); PrintWriter out = response.getWriter(); out.println("<html><body>"); out.println("Hello, world !"); out.println("</body></html>"); } }
Tento servlet nijak nevyužívá informace z přicházejícího požadavku. Nejdříve nastaví typ obsahu na HTML text v kódování UTF-8, pak získá
z objektu reprezentujícího odpověď PrintWriter
, do kterého je možné text zapisovat, a text zapíše.
Kromě textů může servlet generovat i binární výstup, v tom případě se místo getWriter()
používá getOutputStream()
:
public class BinarniVystup extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("application/octet-stream"); ServletOutputStream out = response.getOutputStream(); byte[] obsah = ...; out.write(obsah); } }
Objekt HttpServletResponse
obsahuje všechny nástroje pro vygenerování libovolné HTTP odpovědi, je možné měnit kód výsledku pomocí metody setStatus()
, měnit HTTP hlavičky pomocí addHeader()
, přidávat HTTP cookies pomocí addCookie()
nebo přesměrovat prohlížeč na jinou stránku pomocí sendRedirect()
.
(Ukázka možností servletu je na stránce PA165/Cvičení webové aplikace 1 - Servlet API 3.0.)
Zpracování HTTP požadavku
Objekt HttpServletRequest
obsahuje všechny dostupné informace o HTTP požadavku. Například pro URL
https://franta@www.nekde.cz/mojeapp/mujservlet/mojecesta?p1=A&p2=B&p2=C
obsahuje tyto informace:
metoda | příklad návratové hodnoty | význam |
---|---|---|
getScheme() | https | protokol požadavku |
getMethod() | GET | HTTP metoda |
getRemoteUser() | franta | autentizovaný uživatel |
getServerName() | www.nekde.cz | DNS jméno webserveru |
getServerPort() | 443 | port, na který přišel HTTP požadavek |
getRequestURI() | /mojeapp/mujservlet/mojecesta | celá cesta z URL |
getContextPath() | /mojeapp | část cesty určující webovou aplikaci |
getServletPath() | /mujservlet | část cesty určující servlet |
getPathInfo() | /mojecesta | část cesty za označením servletu |
getQueryString() | p1=A&p2=B&p2=C | queryString z URL |
getRequestURL() | https://www.nekde.cz/mojeapp/mujservlet/mojecesta | URL bez některých částí |
getLocale() | cs | uživatelem preferovaný jazyk |
getCharacterEncoding() | utf-8 | kódování textů |
getContentLength() | 0 | délka těla požadavku |
getContentType() | null | typ těla požadavku u metody POST |
getProtocol() | HTTP/1.1 | verze protokolu |
getRemoteAddr() | 147.251.3.64 | IP adresa stroje s prohlížečem |
getRemoteHost() | acrab.ics.muni.cz | DNS jméno stroje s prohlížečem |
getAuthType() | BASIC_AUTH | způsob autentizace uživatele |
getPathTranslated() | null | umístění souboru na disku, má smysl u JSP |
getRequestedSessionId() | 5469525CA20AD03E97F1AEAD31303B8E | identifikátor session |
Hlavičky HTTP požadavku lze získat pomocí metody getHeader()
. Obsluhu příchozích dat je možné pojmout jedním ze dvou způsobů.
Buď je možné získat tělo požadavku pomocí getInputStream()
a zpracovat ho vlastním kódem, což je nutné
vždy, když typ obsahu je něco jiného než obsah HTML formuláře bez přiloženého souboru.
V obvyklém případě, kdy požadavek vznikl metodou GET nebo POST z HTML formuláře, nabízí HttpServletRequest položky formuláře předzpracované.
Lze je získat pomocí metody getParameter()
nebo getParameterValues()
. Parametry předané v URL v části queryString a parametry předané v těle POST požadavku jsou spojeny do jedné množiny. Jeden parametr může mít více hodnot, pak getParameter()
vrací jen první z nich, kdežto getParameterValues()
vrací všechny. Ve výše uvedeném příkladu tedy výsledky budou:
volání metody | návratová hodnota |
---|---|
getParameter("p1") | A |
getParameter("p2") | B |
getParameterValues("p2") | String[] { "B", "C" } |
České znaky
Rozhraní servletů pamatuje na to, že existují různá kódování ne-ASCII znaků, například různá kódování češtiny.
Aby se české znaky správně zobrazily na HTML stránce, je nutné specifikovat při volání response.setContentType()
(resp. response.setCharacterEncoding()
) nějaké kódování, ve kterém je lze vyjádřit, nejlépe UTF-8 nebo iso-8859-2. Pokud není kódování nastaveno,
použije se iso-8859-1, ve kterém české znaky nemají vyjádření, a proto místo nich budou zobrazeny otazníky.
Prohlížeče vracejí texty z formulářů v tom kódování, ve kterém byla HTML stránka s formulářem. Pokud jsme tedy
například nastavili v setContentType()
kódování UTF-8, dostaneme od prohlížeče parametry v kódování UTF-8.
Bohužel, standardní kódování příchozích parametrů je iso-8859-1, a metoda getParameter()
vrátí nesmyslnou hodnotu, která vznikne použitím UTF-8 bajtů jako iso-8859-1 bajtů.
Pro korektní příjem parametrů je nutné před prvním zavoláním getParameter()
zavolat request.setCharacterEncoding()
,
pak budou parametry překódovány správně.
Podrobnější rozbor problematiky kódování je v hesle I18n - Internacionalizace.
Okolí servletu
Životní cyklus servletu
Servlety jsou Java třídy, mají tedy instance. Každá instance servletu má definovaný životní cyklus. Po jejím vzniku, ale před tím, než je obsloužen první HTTP požadavek, je zavolána metoda init()
, ve které je možné si předpřipravit nějaké zdroje, třeba spojení na databázi. Po ukončení obsluhy požadavků je naopak zavolána metoda destroy()
, ve které je možné předpřipravené zdroje zase uklidit.
Servlet API
Servlet nemůže existovat sám o sobě, potřebuje jisté okolí, které převádí příchozí HTTP požadavky na volání servletu. Proto servlety existují uvnitř tzv. servletového kontejneru. Existují samostatné implementace servletových kontejnerů, např. TomCat, Jetty, nebo může být servletový kontejner součástí většího Java EE aplikačního serveru, např. Glassfish, JBoss, IBM WebSphere atd.
Rozhraní mezi servletem a servletovým kontejnerem je specifikováno v tzv. Servlet API, které je dnes součástí Java Enterprise Edition. V současnosti používané verze této specifikace jsou 2.5 a 3.0, nově přibyla 3.1 .
Servlet | doba vzniku | platforma | implementace | novinky |
---|---|---|---|---|
Servlet 4.0 | září 2017 | JavaEE 8 , JavaSE 8.0 | TomCat 9.x | HTTP/2 |
Servlet 3.1 | květen 2013 | JavaEE 7 , JavaSE 7.0 | TomCat 8.x, Jetty 9.1, Glassfish 4 | Non-blocking I/O, HTTP protocol upgrade mechanism |
Servlet 3.0 | prosinec 2009 | JavaEE 6 , JavaSE 6.0 | TomCat 7.x, Jetty 8, Glassfish 3 | anotace @WebServlet, @WebFilter; asynchronní zpracování; podpora file upload |
Servlet 2.5 | září 2005 | JavaEE 5 , JavaSE 5.0 | TomCat 6.x, Jetty 6, Glassfish | přidává anotace @Resource |
Servlet 2.4 | listopad 2003 | J2EE 1.4, J2SE 1.3 | TomCat 5.x | web.xml používá XML Schema |
Servlet 2.3 | srpen 2001 | J2EE 1.3, J2SE 1.2 | TomCat 4.x | Filter |
Servlet 2.2 | srpen 1999 | J2EE 1.2, J2SE 1.2 | TomCat 3.x | součást J2EE, nezávislé webové aplikace a .war |
Servlet 2.1 | listopad 1998 | nespecifikováno | první oficiální specifikace, RequestDispatcher, ServletContext | |
Servlet 2.0 | JDK 1.1 | Apache JServ 1.0 | součást Java Servlet Development Kit 2.0 | |
Servlet 1.0 | červen 1997 | Apache JServ 0.9 | součást Java Servlet Development Kit 1.0 |
Webová aplikace a kontext servletu
Servlety jsou v rámci servletového kontejneru organizovány do tzv. webových aplikací, jeden kontejner může zároveň obsahovat více nezávislých webových aplikací. Jedna webová aplikace je tvořena jedním souborem s příponou .war, což je ZIP soubor obsahující servlety, další Java třídy, statické soubory (HTML,CSS,GIF,..) a JSP stránky. Tento soubor je při spuštění aplikace obvykle rozbalen do stejnojmeného adresáře. Jeho adresářová struktura je:
/WEB-INF/web.xml ... soubor popisující webovou aplikaci /WEB-INF/classes/*/*.class ... servlety a jiné Java třídy /WEB-INF/lib/*.jar ... JAR balíky /*/*.jsp ... JSP stránky /*/*.* ... HTML, CSS, obrázky atd.
Mapování servletů na URL
Název webové aplikace (název WAR souboru a adresáře z něho vzniklého) je obvykle použit jako začátek cesty v URL odpovídajících této webové aplikaci.
Mapování ve web.xml
Soubor /WEB-INF/web.xml
obsahuje definice mapování URL na servlety, plus další konfigurační údaje. Mapování URL na servlety se provádí
buď podle začátku cesty v URL, nebo podle přípony. Následující příklad určuje, že třída MujServlet
bude zavolána
pro všechna URL začínající prefixem /mujservlet/
, nebo končící příponou .muj
:
<web-app ... ... <servlet> <servlet-name>mujServlet</servlet-name> <servlet-class>cz.nekde.mojeapp.MujServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>mujServlet</servlet-name> <url-pattern>/mujservlet/*</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>mujServlet</servlet-name> <url-pattern>*.muj</url-pattern> </servlet-mapping>
Pokud by tento popisovač web.xml
byl uvnitř souboru s webovou aplikací mojeapp.war
, servlet MujServlet
by byl vyvolán pro URL s cestou začínající /mojeapp/mujservlet/
.
Jeden servlet (Java třída) může mít více instancí. Jedna instance může být mapována na různá URL.
Anotace @WebServlet a web-fragment.xml
Od verze Servlet API 3.0 je možné místo do deployment descriptoru WEB-INF/web.xml uložit stejnou informaci na dvě další místa, do anotací
@WebServlet(name = "MujServlet",urlPatterns = {"/muj/*","*.muj"}) public class MujServlet extends HttpServlet { //... }
nebo do souboru uvnitř některého JAR balíku, tj. WEB-INF/lib/*.jar/META-INF/web-fragment.xml, viz Servlet 3.0 web-fragment.xml.
Filtry
Kromě servletů může webová aplikace obsahovat ješte servletové filtry, což jsou Java třídy implementující interface javax.servlet.Filter, umožňující ovlivnit zpracování HTTP volání dřív, než se dostane k servletům. Do filtrů je vhodné například umístit nastavení kódování českých znaků v požadavku.
ServletContext a inicializační parametry
Webová aplikace se v terminologii Servlet API nazývá kontext servletu, a každý servlet má možnost o ní získat informace pomocí volání getServletContext()
, které vrací objekt ServletContext.
S jeho pomocí je možné například získat informace o servletovém kontejneru (jeho jméně a verzi, implementované verzi Servlet API atd).
Servlety do něj mohou ukládat libovolné objekty a opět je získávat pomocí setAttribute()
a getAttribute()
.
Protože kontext je jen jeden pro celou webovou aplikaci, mohou tímto způsobem servlety sdílet informace mezi sebou.
Celá webová aplikace i jednotlivé instance servletů mohou dostat konfigurační informace ve formě pojmenovaných řetězců, a to pomocí metod getInitParameter()
, které jsou přítomny v interfacech ServletContext
(celá aplikace) a ServletConfig
(jedna instance servletu). Tyto inicializační parametry mohou být nastaveny buď v souboru /WEB-INF/web.xml
,
nebo jiným mechanismem závislým na konkrétní implementaci servletového kontejneru. V každém případě umožňují nastavovat proměnlivé údaje
beze změn programovacího kódu a rekompilace servletů.
HttpSession
HTTP protokol je bezestavový, tj. každý požadavek musí obsahovat všechny potřebné informace. Servlety poskytují nástroj pro identifikaci jednoho prohlížeče pomocí tzv. HttpSession. Požadavky pocházející od stejného prohlížeče jsou identifikovány buď nastavením Cookie, nebo pokud jsou cookies v prohlížeči vypnuta, pomocí URL rewriting (přepisování URL), kdy je do URL vkládán speciální identifikátor.
O sledování prohlížeče pomocí HttpSession se žádá zavoláním metody
HttpSession session = request.getSession(true);
a protože cookies mohou být vypnuta, URL se musí přepisovat pomocí:
String odkaz = response.encodeURL(request.getContextPath() + "/stranka.jsp");
Session se udržuje obvykle 30 minut od posledního požadavku od prohlížeče.
Spolupráce mezi servlety
Příchozí HTTP požadavek může obsloužit jeden servlet sám, nebo je možné použít složitějšího postupu. Zejména při použití sofistikovaných rámců pro tvorbu webových aplikací (např. Apache Struts), je jeden HTTP požadavek zpracováván celou řadou servletů, které si postupně předávají řízení. Například jeden servlet zpracuje příchozí informace, podle výsledku vybere servlet generující HMTL stránku a předá mu řízení. Ten pro generování různých částí stránky (hlavička, navigační menu, patička, atd.) volá různé další servlety.
Předávání řízení mezi servlety se provádí pomocí třídy javax.servlet.RequestDispatcher
, která se získá voláním getRequestDispatcher()
na ServletContext
nebo HttpServletRequest
. Třída RequestDispatcher
umožňuje buď předat řízení úplně pomocí forward()
,
nebo jen dočasně pomocí include()
.
Typicky se toto předání provádí mezi servletem, který zpracoval data, a JSP stránkou (což je druh servletu), která data zobrazí, pomocí kódu:
//předání dat do dalšího servletu request.setAttribute("seznam", Arrays.asList("mléko", "rohlíky", "salám")); //předání požadavku jinému servletu request.getRequestDispatcher("/stranka.jsp").forward(request, response); //konec zpracování return;
Rozsahy platnosti atributů
Spolupracující servlety si mohou předávat informace nastavováním tzv. atributů (protože metody pro nastavování se jmenují setAttribute()
), i když ve speficikacích JSP se používá pojem rozsahová proměnná (scoped variable). Jde o ukládání libovolných objektů pod řetězcovými klíči, tedy do hashovacích tabulek. Tyto atributy je možné nastavovat na více úrovních:
úroveň (scope) | nastavení atributu | existence |
---|---|---|
aplikace | ServletContext.setAttribute() |
v rámci celé aplikace, mezi všemi uživateli |
session | HttpSession.setAttribute() |
ve všech voláních jednoho uživatele |
HTTP požadavek | HttpServletRequest.setAttribute() |
jen po dobu trvání jednoho HTTP požadavku |
JSP stránka | JspContext.setAttribute() |
jen v rámci jedné JSP stránky |
Atributy nastavené na úrovni aplikace jsou společné všem servletům a všem uživatelům.
Session znamená, že všechny HTTP požadavky
přicházející od jednoho webové prohlížeče sdílí instanci HttpSession
,
která umožňuje identifikovat jednoho uživatele. Různí uživatelé mají různé session.
Stejnojmené atributy tedy mohou mít při nastavení do session různé hodnoty pro různé uživatele.
Atributy nastavené na úrovni HTTP požadavku jsou sdíleny řetězcem servletů zpracovávajícím jeden HTTP požadavek.
Platnost v rámci JSP stránky není definována v rámci Servlet API, ale je zde uvedena pro úplnost. V podstatě znamená,
ze atributy v tomto rozsahu platnosti jsou vidět jen v rámci jedné JSP stránky, nepřenáší se do jiných stránek zavolaných pomocí <jsp:include>
nebo <jsp:forward>
. Používají se pro předávání informací mezi JSP tagy.
Listenery
Webová aplikace může ve web.xml definovat, že určité třídy budou dostávat upozornění na určité události.
-
ServletRequestListener
začátek a konec requestu -
ServletRequestAttributeListener
změny atributů requestu -
HttpSessionListener
vytvoření nebo zrušení session -
HttpSessionAttributeListener
změny atributů session -
ServletContextListener
vytvoření a zrušení kontextu aplikace -
ServletContextAttributeListener
změny atributů kontextu aplikace -
HttpSessionActivationListener
aktivace a pasivace session při přenosu mezi VM -
HttpSessionBindingListener
objekt se dozví o svém přidání do session
Upload souborů
Do verze Servlet API 2.5 je nutné řešit upload souborů pomocí externí knihovny, která dokáže zpracovat speciální typ HTTP requestu se souborem.
Od verze 3.0 je podpora upload souborů přímo v Servlet API. Formulář pro odeslání musí mít specifikovaný typ enctype="multipart/form-data a pro každý soubor input typu file:
<form enctype="multipart/form-data" action="/nahrani" method="POST"> <input name="f" type="file"/><br/> <input type="submit" value="Upload"/> </form>
Servlet pak musí mít anotaci @MultipartConfig a soubory získá přes metodu getParts()
:
import javax.servlet.ServletException; import javax.servlet.annotation.MultipartConfig; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.Part; import java.io.IOException; import java.io.PrintWriter; import java.util.concurrent.atomic.AtomicInteger; import java.util.regex.Matcher; import java.util.regex.Pattern; @WebServlet(name = "UploadServlet", urlPatterns = {"/nahrani"}) @MultipartConfig(location = "/tmp", fileSizeThreshold = 1000000, maxFileSize = 5000000) public class UploadServlet extends HttpServlet { static AtomicInteger counter = new AtomicInteger(0); @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/plain; charset=utf-8"); PrintWriter out = resp.getWriter(); for (Part part : req.getParts()) { out.println(); out.println("parameter name: " + part.getName()); out.println("file size: " + part.getSize()); out.println("content-type: " + part.getContentType()); String fileName = "soubor"; String dis = part.getHeader("content-disposition"); if (part.getContentType() != null && dis != null) { Matcher m = Pattern.compile("filename=\"([^\"]*)\"").matcher(dis); if (m.find()) { fileName = m.group(1); out.print("file name=" + fileName); } } part.write(counter.incrementAndGet()+"_"+fileName); } } }
Z bezpečnostních důvodů je třeba omezit maximální velikost přijatého souboru, a uložit soubor pod nějakým jedinečným jménem.
Asynchronní zpracování
Od Servlet API 3.0 je kvůli škálovatelnosti možné zpracovávat requesty a generovat response v jiném vlákně. Smysl to má například, pokud pro vygenerování odpovědi je nutné kontaktovat vnější službu s delší latencí.
@WebServlet(urlPatterns = {"/myasync"}, asyncSupported = true) public class MyAsyncServlet extends HttpServlet { final static Logger log = LoggerFactory.getLogger(MyAsyncServlet.class); private ExecutorService executorService; private BlockingQueue<AsyncContext> queue; @Override public void init() throws ServletException { //create request queue queue = new LinkedBlockingQueue<>(); //create async worker threads int poolSize = Runtime.getRuntime().availableProcessors(); executorService = Executors.newFixedThreadPool(poolSize); for (int i = 0; i < poolSize; i++) executorService.execute(new MyWorker()); } @Override public void destroy() { log.debug("shutdown executor pool"); executorService.shutdownNow(); } @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { log.debug("doGet()"); AsyncContext asyncContext = request.startAsync(); queue.add(asyncContext); } private class MyWorker implements Runnable { @Override public void run() { try { AsyncContext asyncContext = queue.take(); log.debug("got work ..."); //pretend to work for long Thread.sleep(5000); //write response ServletResponse response = asyncContext.getResponse(); ServletOutputStream out = response.getOutputStream(); response.setContentType("text/html;charset=utf-8"); for (int i = 0; i < 12000; i++) out.println("<br>" + i + " hello world"); log.debug("writing completed"); //mark request as completed asyncContext.complete(); } catch (InterruptedException e) { log.trace("e", e); } catch (IOException e) { log.error("e", e); } } } }
Od Servlet API 3.1 jsou navíc k dispozici tzv. neblokující I/O ve formě tříd javax.servlet.ReadListener a javax.servlet.WriteListener.
Shrnutí
Tato stránka je součástí souboru hesel o webových aplikacích. Popisuje Java servlety, což jsou nástroje pro obsluhu HTTP protokolu na straně serveru. Pracují na velice nízké úrovni, takže zatímco umožňují plnou flexibilitu při zpracování HTTP, jsou nepohodlné na programování. Proto je pro tvorbu webových aplikací vhodné použít jejich nadstavby, zejména Java Server Pages a rámce pro tvorbu aplikací, např. Apache Struts. Specifikace Servlet API jsou od jisté verze přímo svázány se specifikacemi Java Server Pages, proto pokračujte tímto tématem.