Inhaltsverzeichnis

In diesem Kapitel bauen wir auf deinem Wissen zu HTTP und Java Server Pages auf und vervollständigen nun das Bild vom Server. Du erfährst hier, wie der serverseitige Quellcode einer Webanwendung überlicherweise gegliedert ist lernst die Programmierung in Java als konkretes Beispiel kennen. Mit diesem Wissen ausgerüstet kannst du dich dann auch in vielen anderen Webframeworks schnell zurechtfinden.

Die drei Arten von Webanwendungen

Bildnachweis für das Smartphone: Pixabay: OpenClipart-Vectors

  • Die komplette Anwendungslogik befindet sich im Browser (JavaScript)
  • Die Anwendung kann lokal komplett ohne Server ausgeführt werden
  • Ein Server wird nur benötigt, wenn die Anwendung über das Internet aufrufbar sein soll
  • Mit Phonegap/Cordova erstelle mobile Anwendungen funktionieren auch nach diesem Prinzip

So haben wir bisher gearbeitet.

  • Der Klassiker: Die Anwendungslogik liegt komplett auf dem Server
  • JavaScript kommt im Browser wenn überhaupt nur sehr sparsam zum Einsatz
  • Stattdessen führt jede Aktion dazu, dass eine neue Seite vom Server geladen wird
  • Der Server führt dabei die Anwendungslogik aus und generiert das anzuzeigende HTML

Das schauen wir uns heute etwas genauer an.

  • Das Beste beider Welten: Browser und Server beinhalten je einen Teil der Anwendungslogik
  • Der HTML-Code der Seite kann zur Laufzeit generiert werden oder in einer einfachen HTML-Datei liegen
  • Bei verschiedenen Aktionen werden mit JavaScript weitere Daten mit dem Server ausgetauscht
  • Die empfangenen Daten werden dabei durch geschickte DOM-Manipulation sichtbar gemacht

Das behandeln wir ein anderes mal.

Was der Server bei einer Anfrage macht

Und nun das Ganze mit Java

import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * Dies ist ein Minimalbeispiel für ein Servlet, das auf HTTP-Anfragen reagiert. */ @WebServlet( urlPatterns = { "/", "/hallo/*", } ) public class MinimalesServlet extends HttpServlet { /** * Diese Methode reagiert auf GET-Anfragen. */ @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { // Datenstrom für Ausgabe zum Client response.setContentType("text/html"); PrintWriter toClient = response.getWriter(); // HTML-Code erzeugen und ausgeben toClient.println("<html>"); toClient.println(" <body>"); toClient.println(" <h1>Hallo Servlet!</h1>"); toClient.println(" </body>"); toClient.println("</html>"); toClient.flush(); } }

Aufgabe 1: Dein erstes Servlet

Lege in Netbeans ein neues Java-Webprojekt an und probiere folgende Sachen aus:

  1. Lösche die Datei index.html aus dem Ordner Web Pages.
  2. Stattdessen lege eine neue Klasse an und kopiere den Quellcode aus Folie 3 hinein.
  3. Starte die Anwendung und rufe das Servlet im Browser auf. Es sollte die Meldung „Hallo Servlet!” erscheinen.
  4. Schreibe ein zweites Servlet, das auf die URL /bye/ reagiert und „Auf Wiedersehen!” anzeigt.

Die wichtigsten Servlet-Methoden

HttpServlet: Das Servlet selbst

public void service(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletEcxeption

Diese Methode kannst du überschreiben, wenn du wirklich auf alle HTTP-Anfragen reagieren willst, egal welches Verb dabei verwendet wurde.

public void doGet(HttpServletRequest request, HttpServletResponse response) … public void doPut(HttpServletRequest request, HttpServletResponse response) … public void doPost(HttpServletRequest request, HttpServletResponse response) … public void doDelete(HttpServletRequest request, HttpServletResponse response) …

Meistens ist es jedoch besser, eine dieser Methoden zu überschreiben. Denn dadurch kannst du gezielt festlegen, wie sich das Servlet bei verschiedenen HTTP-Verben verhalten soll.

/** * Dieses Beispiel zeigt die Verwendung der verschiedenen doXXX-Methoden. */ @WebServlet(urlPatterns = {"/"}) public class GetPostServlet extends HttpServlet { /** * Wir haben eine GET-Anfrage empfangen. */ @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { … } /** * Wir haben eine POST-Anfrage empfangen. */ @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { … } }

HttpServletRequest: Die HTTP-Anfrage

public String getRequestURI()

Liefert den Pfad der URL, so wie er in der HTTP-Anfrage geschickt wurde. Der String beinhaltet daher auch den Namen der Webanwendung, mit dem ja jeder URL beginnt (siehe Screenshot).

public String getQueryString()

Liefert die Anfrageparameter aus der URL oder null, falls die URL keine Anfrageparameter enthält.

public String getParameter(String name)

Liefert den Wert eines einzelnen Anfrageparameters aus der URL. Alternativ kann damit auch der Wert eines Formularfelds ausgelesen werden, wenn ein HTML-Formular abgeschickt wurde.

public String getHeader(String name)

Liefert den Wert eines einzelnen Header Fields aus der HTTP-Anfrage oder null, wenn es das Feld nicht gibt.

public HttpSession getSession()

Liefert den sogenannten Session Kontext zurück. Diesen kannst du als Notizblock nutzen 🗒️, um dir über eine Anfrage hinaus benutzerspezifische Werte zu merken.

public RequestDispatcher getRequestDispatcher(String URL)

Dieses Monster benötigen wir später, um die Bearbeitung einer HTTP-Anfrage an eine Java Server Page weiterzugeben. 👾

/** * Dieses Beispiel zeigt den Aufruf einiger wichtiger Methoden * von HttpServletRequest. */ @WebServlet(urlPatterns = {"/"}) public class RequestMethodenServlet extends HttpServlet { /** * Wir haben eine GET-Anfrage empfangen. */ @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { // Informationen aus der HTTP-Anfrage auslesen String uri = request.getRequestURI(); String query = request.getQueryString(); String vornameParameter = request.getParameter("vorname"); String userAgent = request.getHeader("user-agent"); // Antwort senden response.setContentType("text/plain"); PrintWriter toClient = response.getWriter(); toClient.println("Folgende Anfrage hast du geschickt"); toClient.println("=================================="); toClient.println(""); toClient.println("URL: " + uri); toClient.println("Query String: " + query); toClient.println("Parameter vorname: " + vornameParameter); toClient.println("User Agent: " + userAgent); toClient.flush(); } }

HttpServletResponse: Die HTTP-Antwort

public void setStatus(int code) public void sendError(int code)

Setzt den Statuscode der HTTP-Antwort. Für jeden Statuscode gibt eine entsprechende Konstante, zum Beispiel response.SC_NOT_FOUND. Im Gegensatz zu setStatus() schickt sendError() die Antwort mit einer Standardfehlerseite sofort ab.

public void sendRedirect(String url)

Sendet den Stautscode 302 Redirect an den Browser und fordert ihn damit auf, eine neue Seite zu laden.

void setContentType(String type)

Setzt das Header Field content-type, um dem Browser mitzuteilen, welches Format die zurückgelieferten Daten haben. Der Wert type muss einem gültigen MIME-Type entsprechen.

public PrintWriter getWriter()

Liefert einen PrintWriter zurück, mit dem der Response Body geschrieben werden kann. Wichtig: Sobald du anfängst, etwas in den Datenstrom zu schreiben, kannst du keine anderen Methoden von HttpServletResponse mehr aufrufen, da dadurch der Response Header abgeschlossen wird.

/** * Dieses Beispiel leitet den Aufrufer immer an Google weiter. */ @WebServlet(urlPatterns = {"/"}) public class RedirectServlet extends HttpServlet { /** * Wir haben eine GET-Anfrage empfangen. */ @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { response.sendRedirect("https://www.google.de"); } }

Beispiel: Formulareingaben verarbeiten

import java.io.IOException; import java.io.PrintWriter; 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 javax.servlet.http.HttpSession; /** * Dieses Servlet zeigt, wie du Formulareingaben richtig behandelt werden. * Es zeigt, dass bei einer GET-Anfrage das Formular geschickt wird und * dieses seine Daten per POST an den Server schickt. Bei einer POST-Anfrage * müssen daher die Formulardaten ausgelesen und verarbeitet werden, woraufhin * der Browser durch einen Redirect zum Neuladen der Seite gezwungen wird. */ @WebServlet(urlPatterns = {"/formular/"}) public class FormularServlet extends HttpServlet{ /** * GET-Anfrage: Liefert eine HTML-Seite mit einem Formular */ @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { // Anfang der HTML-Seite response.setContentType("text/html"); response.setCharacterEncoding("utf-8"); PrintWriter toClient = response.getWriter(); toClient.println("<!DOCTYPE html>"); toClient.println("<html>"); toClient.println(" <head>"); toClient.println(" <meta charset='utf-8' />"); toClient.println(" <title>Hallo-Welt-Formular</title>"); toClient.println(" </head>"); toClient.println(" <body>"); // Hier nun das eigentliche Formular toClient.println(" <form method='POST'>"); toClient.println(" Wie heißt du?"); toClient.println(" <input name='vorname' type='text' />"); toClient.println(" <input type='submit' value='Abschicken' />"); toClient.println(" </form>"); // Zuletzt eingegebener Vorname, falls vorhanden // Der Wert wird in der doPost()-Methode im Session Kontext abgelegt HttpSession session = request.getSession(); String vorname = (String) session.getAttribute("vorname"); if (vorname != null) { toClient.println(" <p>"); toClient.println(" Hallo, " + vorname + "!"); toClient.println(" </p>"); } // Abschluss der Seite toClient.println(" </body>"); toClient.println("</html>"); toClient.flush(); } /** * POST-Anfrage: Verarbeitet die Formulareingaben und zwingt den Browser * danach, die Seite neuzuladen. */ @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { // Eingebenen Vornamen auslesen String vorname = request.getParameter("vorname"); // Vornamen im Session Kontext speichern HttpSession session = request.getSession(); session.setAttribute("vorname", vorname); // Browser zwingen, die Seite mit einem GET neuzuladen response.sendRedirect(request.getRequestURI()); } }

Der HTML-Code aus Sicht des Browsers nach dem ersten Aufruf. In den Entwicklerwerkzeugen wurde nur eine GET-Anfrage aufgezeichnet.

<!DOCTYPE html> <html> <head> <meta charset='utf-8' /> <title>Hallo-Welt-Formular</title> </head> <body> <form method='POST'> Wie heißt du? <input name='vorname' type='text' /> <input type='submit' value='Abschicken' /> </form> </body> </html>

Der neue HTML-Code aus Sicht des Browsers. In den Entwicklerwerkzeugen sieht man schön, wie erst ein POST und dann ein GET an den Server gesendet wird.

<!DOCTYPE html> <html> <head> <meta charset='utf-8' /> <title>Hallo-Welt-Formular</title> </head> <body> <form method='POST'> Wie heißt du? <input name='vorname' type='text' /> <input type='submit' value='Abschicken' /> </form> <p> Hallo, Alf! </p> </body> </html>

Aufgabe 2: Ein kleines Servlet-Quiz

Aufgabe 1.1: Grundlagen

1. Wie werden Request Handler in Java allgemein genannt?

  1. HTTP Handler
  2. Java Server Pages
  3. Servlets

2. Auf welche zwei Arten kann das URL-Routing in Java definiert werden?

  1. Mit der Annotation @WebServlet
  2. Mit der Annotation @UrlPatterns
  3. Im Konstruktor eines Servlets
  4. In der Datei WEB-INF/web.xml

3. Welchen Zweck erfüllen die Request Handler einer Webanwendung?

  1. Sie implementieren verschiedene Protokolle wie SFTP oder IMAP
  2. Sie nehmen HTTP-Anfragen entgegen und erzeugen eine HTTP-Antwort
  3. Sie verarbeiten die vom Server empfangene Daten mit JavaScript

4. Welchen Zweck erfüllen Templates in einer Webanwendung

  1. Sie sollen die serverseitige Erzeugung von HTML-Code vereinfachen
  2. Sie sollen die Verarbeitung komplexer HTTP-Anfragen ermöglichen
  3. Sie dienen als Vorlage für neue Webanwendungen

5. Was ist das Post/Redirect/Get-Pattern?

  1. Eine spezielles Kontaktformular auf einer Firmenwebseite
  2. Ein empfohlenes Vorgehen bei der Weiterentwicklung alter Servletklassen
  3. Ein Schema, wie Formulareingaben sinnvoll behandelt werden können

6. Welche Aufgabe hat der Session Kontext

  1. Daten im Browser zwischenspeichern, um sie später an den Server zu schicken
  2. Serverseitig je Benutzer bestimmte Daten über mehrere Anfragen hinweg zwischenspeichern
  3. Serverseitig über alle Benutzer hinweg globale Daten zwischenspeichern

Aufgabe 1.2: Wichtige Methoden

1. Wie heißt die Methode eines Servlets, die auf alle HTTP-Anfragen reagiert?

  1. doAll()
  2. service()
  3. handle()

2. Welche beiden Parameter bekommen die do…-Methoden eines Servlets übergeben?

  1. Ein HttpRequest- und ein HttpResponse-Objekt
  2. Zwei Datenströme zum Lesen und Schreiben der HTTP-Anfrage bzw. Antwort
  3. Ein HttpServletResponse- und ein HttpServletRequest-Objekt
  4. Ein HttpServletRequest- und ein HttpServletResponse-Objekt

3. Mit welcher Methode kann die angefragte URL ermittelt werden?

  1. request.getRequestURI()
  2. response.getRequestURI()
  3. this.getRequest().getURI()

4. Mit welchen Methoden kann der Statuscode der HTTP-Antwort gesetzt werden?

  1. response.setStatus()
  2. response.sendError()
  3. response.setStatusCode()
  4. response.getResponse().setStatus()

5. Mit welcher Methode erhält man einen Datenstrom zum Schreiben des HTTP Response Body?

  1. request.response.getWriter()
  2. request.getInputStream()
  3. response.setOutputStream()
  4. response.getWriter()
  5. response.getBodyStream()

6. Mit welcher Methode kann bekommt man Zugriff auf den Session Kontext?

  1. this.getSessionContext()
  2. this.getSession()
  3. request.getSession()
  4. request.session.get()

Lösung: Aufgabe 1.1: 3, 1+4, 2, 1, 3, 2. Aufgabe 1.2: 2, 4, 1, 1+2, 4, 3.

Weiterleitung an eine Java Server Page

So sieht das Netbeans-Projekt aus. Wenn du es nachbauen willst, musst du exakt die Verzeichnisstruktur einhalten.

Dies ist das Servlet, dass bei jeder GET- und POST-Anfrage durchlaufen wird.

import java.io.IOException; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import javax.servlet.RequestDispatcher; 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 javax.servlet.http.HttpSession; /** * Dieses Servlet zeigt, wie die Erzeugung des HTML-Codes an eine Java Server * Page ausgelagert werden kann. */ @WebServlet(urlPatterns = {"/index.html"}) public class HalloServlet extends HttpServlet { @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { // Zwischenspeichern eines Wertes, den wir in der JSP anzeigen wollen // Die letzte Zeile ist die wichtige Zeile hier DateFormat dateFormat = new SimpleDateFormat("dd.MM.yyyy"); Date date = new Date(); String tagesdatum = dateFormat.format(date); request.setAttribute("tagesdatum", tagesdatum); // Anfrage an eine JSP weiterleiten, um damit den HTML-Code // der Seite zu generieren RequestDispatcher dispatcher = request.getRequestDispatcher("/WEB-INF/hallo.jsp"); dispatcher.forward(request, response); // Werte im Session Kontext entfernen, damit wir beim nächsten mal // wieder von vorne anfangen HttpSession session = request.getSession(); session.removeAttribute("vorname"); session.removeAttribute("nachname"); } @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { // Eingegebene Werte auslesen String vorname = request.getParameter("vorname"); String nachname = request.getParameter("nachname"); // Werte im Session Kontext ablegen HttpSession session = request.getSession(); session.setAttribute("vorname", vorname); session.setAttribute("nachname", nachname); // Und die Seite nochmal laden lassen response.sendRedirect(request.getRequestURI()); } }

Innerhalb des Servlets wird nun kein HTML-Code mehr erzeugt. Das macht die folgende JSP für uns. Die Datei muss WEB-INF/hallo.jsp heißen!

<%@page contentType="text/html" pageEncoding="UTF-8"%> <%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> <%@taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"%> <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Hallo, Welt!</title> <link rel="stylesheet" href="style.css" /> </head> <body> <div class="container"> <!-- Eingabefelder, nur vor dem Abschicken anzeigen --> <c:if test="${vorname == null}"> <h1>Wie heißt du?</h1> <form method="POST"> <input name="vorname" type="text" placeholder="Vorname" /> <input name="nachname" type="text" placeholder="Nachname" /> <input type="submit" value="Abschicken" /> </form> </c:if> <!-- Begrüßung, nur nach dem Abschicken anzeigen --> <c:if test="${vorname != null}"> <h1>Hallo, ${vorname} ${nachname}!</h2> <h2>Sei gegrüßt!</h2> <a href="">Nochmal</a> </c:if> <!-- Aktuelles Datum immer anzeigen --> <p> <small> Heute ist der ${tagesdatum}. </small> </p> </div> </body> </html>

Dieses mal gönnen wir uns auch ein Stylesheet, damit es ordentlich aussieht. Die Datei muss style.css heißen.

html, body { font-family: sans-serif; font-size: 12pt; height: 100%; margin: 0; padding: 0; } html { background-image: url(https://picsum.photos/1024/?image=1067); background-repeat: no-repeat; background-position: center center; background-size: cover; } body { display: flex; justify-content: center; align-items: center; } .container { background: rgba(255,255,255,0.8); border-radius: 0.5em; padding: 1em; margin: 2em; } h1, h2 { color: crimson; text-shadow: 0 0 1px rgba(0,0,0,0.5); margin: 0 0 0.5em 0; } h2 { color: deepskyblue; } input[type="text"] { display: block; width: 25em; box-sizing: border-box; border: 1px solid grey; margin-bottom: 0.5em; }

Und so sieht die Anwendung aus, wenn sie fertig ist.

Aufgabe 3: Formularprüfungen ergänzen

Aufgabe

Kopiere den Quellcode aus der vorherigen Folie in ein neues Netbeansprojekt und nimm folgende Änderungen daran vor:

  • Wie immer, lösche die von Netbeans automatisch erzeugte Datei index.html.
  • Zunächst soll in doPost() geprüft werden, ob beide Felder ausgefüllt wurden.
  • Sind die Eingaben okay, soll die bekannte Bestätigungsseite angezeigt werden.
  • Fehlt eines der beiden Felder, soll das Formular mit einer Fehlermeldung stehen bleiben.
  • Achte dabei darauf, dass die bisherigen Eingaben nicht verloren gehen!
  • Eine Prüfung mit JavaScript soll nicht erfolgen. Diese sparen wir uns hier. 🙂

Hinweise

  • Du musst ein Kennzeichen im Session Kontext ablegen, ob die Prüfung erfolgreich war.
  • Wird ein Wert nicht gefunden, liefert die Expression Language immer null zurück.
  • Wenn du eine Liste mit Fehlermeldungen im Session Kontext hast, kannst du sie wie folgt ausgeben:

    <c:forEach items="${fehlermeldungen}" var="fehlermeldung"> ${fehlermeldung} </c:forEach>

doPost()-Methode im Servlet

Die neue doPost()-Methode sieht wie folgt aus. Neu hinzugekommen sind die beiden Attribute fehlermeldungen und ergebnisAnzeigen im Session Kontext.

@Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { // Eingegebene Werte auslesen List<String> fehlermeldungen = new ArrayList<>(); String vorname = request.getParameter("vorname"); String nachname = request.getParameter("nachname"); if (vorname == null || vorname.isEmpty()) { fehlermeldungen.add("Gib erst einen Vornamen ein"); } if (nachname == null || nachname.isEmpty()) { fehlermeldungen.add("Gib erst einen Nachnamen ein"); } // Werte im Session Kontext ablegen HttpSession session = request.getSession(); session.setAttribute("vorname", vorname); session.setAttribute("nachname", nachname); session.setAttribute("fehlermeldungen", fehlermeldungen); session.setAttribute("ergebnisAnzeigen", fehlermeldungen.isEmpty()); // Und die Seite nochmal laden lassen response.sendRedirect(request.getRequestURI()); }

Java Server Page

Die Java Server Page muss wie folgt angepasst werden:

<!-- Eingabefelder, nur vor dem Abschicken anzeigen --> <c:if test="${ergebnisAnzeigen != true}"> <h1>Wie heißt du?</h1> <form method="POST"> <input name="vorname" type="text" placeholder="Vorname" value="${vorname}" /> <input name="nachname" type="text" placeholder="Nachname" value="${nachname}" /> <input type="submit" value="Abschicken" /> </form> <ul class="error"> <c:forEach items="${fehlermeldungen}" var="fehlermeldung"> <li>${fehlermeldung}</li> </c:forEach> </ul> </c:if> <!-- Begrüßung, nur nach dem Abschicken anzeigen --> <c:if test="${ergebnisAnzeigen == true}"> … </c:if>

Stylesheet

Im Stylesheet muss eine neue Klasse für Fehlermeldungen definiert werden:

.error { color: red; font-weight: bold; }

Rechtshinweise

Creative Commons Namensnennung 4.0 International

§