Java Servlets och JavaServer Pages

Specifikationerna för de aktuella versionerna (Java Servlets 2.3 och JavaServer Pages 1.2) kan laddas hem från http://www.jcp.org/aboutJava/communityprocess/final/jsr053/.

API-dokumentationen för servlets finns online på http://java.sun.com/j2ee/sdk_1.3/techdocs/api/. De paket som är aktuella för servlets är javax.servlet och javax.servlet.http.

Servern Orion kan laddas hem på http://www.orionserver.com/. Exemplen på denna sida är provkörda med Orion 2.0.2.

Denna sida är en mycket kortfattad introduktion, det finns väldigt mycket mer att säga om alla områden som nämns på sidan.

HTTP och program på serversidan

Java Servlets och JavaServer Pages (JSP) är de Java-specifikationer som finns för att exekvera program i en HTTP-server för att generera dynamiska svar (till exempel i form av HTML). För att förstå hur de används är det nödvändigt att ha grundläggande kunskaper om HTTP.

Fall 1: HTTP-servern levererar statiska filer

När vi skriver in en URL eller klickar på en länk i en browser kommer den att skicka en HTTP-request. Om vi till exempel vill titta på sidan http://www.isk.kth.se/kursinfo/6b2015/index.html kommer browsern att skicka en request till servern www.isk.kth.se om att få innehållet på sidan /kursinfo/6b2015/index.html. Servern kommer då att läsa in filen index.html i katalogen kursinfo/6b2015 och skicka dess innehåll till browsern som tolkar det och visar det.

Fall 2: HTTP-servern levererar ett dynamiskt genererat svar

Allt browsern gjorde i fall 1 ovan var att skicka en sträng med ett sidnamn till servern och få en sträng med HTML-kod till svar. Ur browserns synpunkt är det helt ointressant om det verkligen finns en fil i serverns filsystem som heter index.html. Om svaret genereras dynamiskt finns ingen sådan fil utan servern använder i stället namnet på sidan browsern vill se till att identifiera ett program. Servern exekverar detta program och skickar utskriften från det till browsern. Browsern tolkar svaret som (till exempel) HTML och visar det för användaren.

JavaServer Pages (JSP)

Vad är en JSP och när ska de användas?

JSP är en standard för att skriva program i Java som ska exekveras av en HTTP-server enligt fall två ovan. Det finurliga med JSP är att vi inte behöver skriva själva Java-koden utan den automatgenereras av servern enligt instruktionerna i JSP-sidan. JSP definierar ett antal olika taggar som styr vilken Java-kod som genereras. Allt annat i sidan än dessa taggar skickas oförändrat till browsern när Java-programmet som genererats utifrån JSP-sidan exekveras.

JSP ska användas för att dynamiskt generera textbaserade sidor (tex HTML-sidor). Dessa sidor kan naturligtvis innehålla bilder och allt annat som "vanliga" HTML-sidor kan innehålla. Med "textbaserade" menas att vi inte ska använda JSP för att generera tex själva bilden (tex JPEG-kod).

Att exekvera en enkel JSP (hello world)

I sin enklaste form innehåller en JSP inga av de taggar som används för att styra omvandlingen av den till ett Java-program. Det innebär att allt dess innehåll skickas till browsern och att den ser ut som en vanlig HTML-sida. Här kommer ett exempel på en sådan JSP, hello-world.jsp.

Med hjälp av denna fil ska vi nu gå igenom hur en JSP deployas i Orion. Att få igång en ny server är oftast ganska klurigt. Underskatta alltså inte tidsåtgången för detta och bli heller inte bedrövad om det tar ett par dagar innan hello world fungerar. Det är fullt normalt.

I nedanstående instruktioner syftar ORION_HOME på den katalog där Orion installerats, till exempel c:\Program\Orion eller /usr/local/orion.

  1. Ladda hem Orion från länken överst på sidan och installera den enligt anvisningarna på http://www.orionserver.com/docs/install.html. Kolla att det går att starta den enligt punkt åtta i Step-by-step Installation Guide på sidan ovan. Notera att du måste byta port servern lyssnar på (enligt punkt fyra i Step-by-step Installation Guide) om du redan har någon annan server som lyssnar på port 80. I UNIX/Linux är det dessutom bara root som har rättighet att använda portnummer under 1024. Om du vill köra Orion på port 80 måste du alltså logga in som root varje gång du ska starta servern.

  2. Skapa en katalog för den nya webapplikationen (hello world). Denna kan heta helloworld och ska ligga i ORION_HOME/applications.

  3. Placera filen hello-world.jsp i katalogen ORION_HOME/applications/helloworld som skapades i föregående punkt.

  4. Skapa en fil som heter web.xml och ligger i katalogen ORION_HOME/applications/helloworld/WEB-INF (som måste skapas först). Denna fil kallas deployment descriptor och kan innehålla information till servern om en webapplikation. Vi återkommer till den senare. I det här exemplet behöver den inte innehålla någon information, det räcker att den finns och innehåller en enda rad där det står
    <web-app></web-app>

  5. Nu måste vi tala om för Orion att vi har skapat en ny webapplikation. Det gör vi genom att i filen ORION_HOME/config/application.xml lägga till raden
    <web-module id="helloworld" path="../applications/helloworld"/>
    Värdet av path måste vara sökvägen från application.xml till rotkatalogen för webapplikationen, dvs den katalog där hello-world.jsp placerades. Min application.xml ser nu ut så här, application.xml.

  6. Till slut måste vi koppla vår webapplikation till en web site på Orion. Samma Orion-server kan lyssna på flera olika portar och IP-adresser och varje unik port är en web site. Vi behöver inte krångla till det genom att gräva djupare i detta utan låter alla våra webapplikationer tillhöra Orions default web site, den som användes i punkt ett ovan. Vi kopplar vår webapplikation till den genom att i filen ORION_HOME/config/default-web-site.xml lägga till raden
    <web-app application="default" name="helloworld" root="/hello-world"/>
    Alla webapplikationer i Orion måste tillhöra en J2EE-applikation. Även detta kan vi strunta i genom att låta alla våra webapplikationer tillhöra default-applikationen (det var den som vi konfigurerade i punkt 5). För att de ska tillhöra default-applikationen måste värdet av application vara just default. Värdet av name måste vara exakt detsamma som värdet av id i raden vi lade in i punkt fem. Värdet av root blir början på path:en i URL:en för vår webapplikation. Eftersom värdet av root är /hello-world kommer vi att hitta vår webapplikation på http://localhost:8080/hello-world (om servern finns på localhost och lyssnar på port 8080). Observera att värdet på root måste börja med en slash (/). Min default-web-site.xml ser ut så här, default-web-site.xml.

  7. Nu går det att surfa till våran JSP. Starta en browser och surfa till http://localhost:8080/hello-world/hello-world.jsp (om servern finns på localhost och lyssnar på port 8080). Förhoppningsvis får du då uppleva tillfredsställelsen att se den hett efterlängtade texten Hello World!!
Trots att det i browsern ser ut exakt som om en statisk fil med innehållet i hello-world.jsp hade skickats från servern är det inte alls så det går till. Servern har i stället, enligt vad som sades i avsnittet Fall 2: HTTP-servern levererar ett dynamiskt genererat svar ovan omvandlat hello-world.jsp till ett Java-program, kompilerat det, exekverat det och skickat dess utskrift till browsern. Om attributet development="true" läggs till elementet <orion-web-app> i filen ORION_HOME/application-deployments/default/helloworld/orion-web.xml kommer källkoden för Java-programet att sparas. Min orion-web.xml ser ut så här, orion-web.xml. Efter att du gjort denna ändring måste du starta om servern, ändra i hello-world.jsp och surfa till hello-world.jsp. Därefter hittar du den automatgenererade Java-klassen i filen ORION_HOME/application-deployments/default/helloworld/persistence/hello-world.jsp.java Mitt program ser ut så här, hello-world.jsp.java Enligt specificationen för JSP måste klassen innehålla en metod som heter _jspService(). Denna metod måste anropas av servern varje gång en browser frågar efter sidan. Vi kan se att denna metod hämtar ett objekt av utströmmen JspWriter och anropar metoden write för att skriva till strömmen. Allt som skrivs dit kommer att skickas till browsern. I det här fallet är det HTML-koden vi skrev i hello-world.jsp som skickas.

Om hello-world.jsp eller web.xml byts ut kommer de nya filerna automatiskt att laddas av Orion, utan att servern behöver startas om. Kolla gärna genom att lägga in en förändrad hello-world.jsp och klicka på reload i browsern.

Inkludera filer i en JSP

En JSP kan inkludera en annan sida. Det görs med direktivet <%@include file="sökväg till filen som ska inkluderas relativt den inkluderande filen"%>. Detta innebär att innehållet (dvs "källkoden") i den inkluderade filen läggs in i den inkluderande filen på det ställe där include-taggen står. Det är alltså inte resultatet av exekveringen av den inkluderade filen som läggs in.

Inkludering är användbart om samma fragment (till exempel en header, footer eller meny) ska finnas på flera olika sidor. Här kommer ett exempel, filen includer.jsp inkluderar filen footer.html.

Om vi tittar i källkoden till den java-klass Orion genererar (includer.jsp.java) ser vi att innehållet i footer.html hamnar i ett anrop av write() precis som innehållet i includer.jsp.

Anropa Java-objekt från en JSP

Det finns taggar definierade i JSP för att anropa andra Java-objekt. Dessa objekt måste då uppfylla vissa delar av JavaBeans-specifikationen. Det finns ingen anledning att gå igenom den här, det räcker att fastslå de krav objekten måste uppfylla:
  1. De måste ha en konstruktor som inte tar några inparametrar. Den kommer att anropas när ett objekt instansieras.
  2. De måste gå att serialisera.
  3. De metoder som ska anropas från en JSP måste följa JavaBeans-definitionen av properties. Detta innebär att de måste ha publik åtkomst och heta setXXX() eller getXXX(). Hur XXX lagras eller beräknas spelar ingen roll.
Vi gör ett objekt synligt för en JSP med hjälp av taggen <jsp:useBean id="myBean" class="ai1.serverjava.beanexample.MyBean" scope="application"/>. Det finns många olika sätt att skriva den men i den här korta genomgången nöjer vi oss med detta sätt. Värdet av id blir namnet på det objekt som skapas och class ska ange klassnamnet inklusive paket på den klass objektet tillhör. scope anger hur länge objektet ska finnas kvar. Defaultvärdet är page som innebär att det bara finns på den sida som innehåller taggen jsp:useBean. Det går även att ange request som innebär att det finns kvar tills svaret är levererat till browsern, session som innebär att det existerar under en session (se nedan) och application som betyder att det finns kvar under hela webapplikationens existens.

Ett anrop av en set-metod i bönan genereras av taggen <jsp:setProperty name="myBean" property="beanProperty" param="requestParam"/> där värdet av name måste vara detsamma som värdet av id i den jsp:useBean som deklarerade bönan. property anger namnet på den metod som ska anropas, om property har värdet xyz kommer metoden setXyz() att anropas. Slutligen måste värdet på metodens inparameter anges. Med attributet param ges namnet på en requestparameter (från tex ett HTML-formulär) vars värde blir indata till set-metoden. Alla requestparametrar är av typen java.lang.String men de omvandlas automatiskt om de ska skickas till metoder som hanterar properties av andra typer. I stället för atributet param kan attributet value anges. Dess värde blir då det värde som ska skickas till metoden. Om värdet är en String kan det omvandlas på samma sätt som när attributet param används. Om värdet inte är en String måste det vara av samma typ som property:n som ska sättas.

Ett anrop av en get-metod i bönan genereras av taggen <jsp:getProperty name="myBean" property="beanProperty"/> där värdet av name måste vara detsamma som värdet av id i den jsp:useBean som deklarerade bönan. property anger namnet på den metod som ska anropas, om property har värdet xyz kommer metoden getXyz() att anropas. I svaret till browsern kommer taggen att ersättas av det som returneras av metoden getXyz(). Om den inte returnerar en String kommer det returnerade värdet automatiskt att omvandlas till en String.

Här kommer ett exempel, JSP:n use-bean.jsp använder bönan MyBean.java. MyBean.java ska kompileras och class-filen ska placeras under katalogen ORION_HOME/applications/jspbean/WEB-INF/classes i en katalogstruktur som stämmer med paketnamnet. jspbean är webapplikationens rotkatalog, den som motsvaras av katalogen helloworld i exemplet ovan där hello-world.jsp deployades. Om paketet (som i MyBean.java) heter ai1.serverjava.beanexample ska class-filen placeras i ORION_HOME/applications/jspbean/WEB-INF/classes/ai1/serverjava/beanexample

Låt oss till sist titta i den Java-klass som Orion skapar, use-bean.jsp.java. Vi kan se att den översätter taggen jsp:useBean till att deklarera en variabel av typen ai1.serverjava.beanexample.MyBean som heter myBean, försöka hämta ett sådant objekt och, om det inte lyckas, skapa det. Vi kan också se hur den översätter jsp:setProperty och jsp:getProperty till anrop av set- respektive get-metoder i objektet myBean.

Observera till sist att om filen MyBean.class byts ut måste Orion startas om.

HTTP-sessioner

Ibland måste vårat program på servern veta om samma användare varit där tidigare och vad hon/han gjorde då. Ett bra exempel är om användaren måste logga in på något sätt. Om så är fallet måste vi, när ett request kommer, kunna kolla om den som gör det requestet har loggat in i ett tidigare request eller inte. Problemet är att vartenda request kör exakt samma kod i en egen tråd, var ska vi då spara informationen om användarens tidigare förehavanden?

Ett smidigt sätt att lösa detta är att låta JSP:n delta i en HTTP-session. Att den ska göra det anges genom att lägga till attributet session="true" i direktivet page. Om JSP:n deltar i en session går det att spara objekt (till exempel JavaBeans) under exakt sesionens livslängd. Ett nytt objekt kommer då att automatiskt skapas när en ny session startas och automatiskt kastas när sessionen slutar. Hur detta sköts av servern får vi hoppa över i den här korta genomgången.

Default för Orion lever en session från att en sida som deltar i sessionen accessas tills browsern stängs. Sessionen överlever en omstart av servern. Default hanteras sessioner i JSP med cookies, men den hanteringen sköts helt av servern.

Här kommer ett exempel på en JSP som deltar i en session, session.jsp. Den använder en böna, CounterBean.java, för att räkna hur många gånger sidan accessats i en och samma request, session och webapplikation. Vad som räknas styrs av vilket scope som angetts för bönan i jsp:useBean. Titta på sidan i en browser och tryck på reload några gånger. Räknaren för session och applikation ökar medans antalet accesser i requesten aldrig blir mer än ett. Det beror på att varje anrop till servern är en ny request. Prova att stänga av servern och starta den igen. Sessionsräknaren nollställs inte eftersom sessioner överlever en omstart av servern, däremot startas webapplikationen om på nytt varje gång servern startar. Detta gör att applikationsräknaren börjar om på ett. Prova till sist att avsluta browsern och starta den igen. Nu nollställs sessionsräknaren men applikationen lever tills servern stängs.

Java Servlets

Vad är en servlet och när ska de användas?

En servlet är, precis som en JSP, ett Java-program som körs av en HTTP-server till följd av ett HTTP-request. Den stora skillnaden är att en servlet skrivs direkt som ett Java-program, i stället för att programmet automatgenereras av servern.

En servlet har två användningsområden. Det absolut vanligaste är att den används som controller i en webapplikation, dvs att den tar emot HTTP-request, utför de operationer på servern som krävs och sedan vidarebefodrar requestet till den JSP som utgör nästa skärmbild i browsern. Det andra användningsområdet är att skicka binärt data till browsern. Ett bra exempel på det är om en bild (som inte finns i filsystemet) ska genereras. Servleten kan då göra det och sedan skicka det binära datat (dvs bilden).

Ett exempel på en servlet

En servlet är som sagt en "vanlig" Java-klass som vi skriver själva. Den ska ärva av den abstrakta klassen javax.servlet.http.HttpServlet. Om någon form av initiering ska göras ska den ske genom att metoden public void init() omdefinieras. Det ska inte finnas någon konstruktor i en servlet. Containern kommer att anropa init() exakt en gång för varje servletobjekt som instansieras.

Om det kommer ett HTTP GET-request (vanligtvis pga av att användaren klickat på en länk eller skrivit in en URL) kommer containern att anropa metoden public void doGet(HttpServletRequest req, HttpServletResponse resp). Objektet req hanterar requestet och objektet resp hanterar svaret som ska skickas till browsern. Om det kommer ett HTTP POST-request (vanligtvis pga av att användaren fyllt i och skickat ett HTML-formulär) kommer containern att anropa metoden public void doPost(HttpServletRequest req, HttpServletResponse resp). Ofta finns det ingen anledning att behandla post och get olika, då är det lämpligt att låta dessa två metoder innehålla en enda rad som anropar samma privata metod där allt arbete görs.

Servlet-api:et innehåller väldigt mycket mer än så, ta gärna en titt på api-dokumentationen i länken överst på sidan, men det här är allt som behövs för att komma igång.

Här kommer en enkel servlet som inte gör något mer än att skriva till System.out när den anropas, SysOutServlet.java. Utskriften kommer att synas i det fönster servern startades, i browsern händer ingenting. För att kompilera servleten måste klassbiblioteket för servlet-api:et finnas på classpath. Du kan till exempel stå i den katalog där SysOutServlet.java finns och skriva
javac -classpath .:ORION_HOME/orion.jar SysOutServlet.java 
I Windows används ; i stället för : för att skilja olika sökvägar i classpath.

Vi väntar med att deploya servleten tills vi tittat på deployment descriptorn i nästa avsnitt.

Deployment descriptor

Deployment descriptorn, som används för att konfigurera och deklarera innehållet i en webapplikation, utgörs av filen web.xml. Hittils har den filen varit helt tom, men nu när webapplikationen innehåller en servlet måste deployment descriptorn innehålla en deklaration av servleten. En servlet deklareras genom att följande rader läggs in i web.xml
  <servlet>
<servlet-name>SysOutServlet</servlet-name>
<servlet-class>ai1.serverjava.servlet.SysOutServlet</servlet-class>
</servlet>
Detta talar om att det finns en servlet som heter SysOutServlet och finns i klassen ai1.serverjava.servlet.SysOutServlet. Min web.xml ser nu ut så här, web.xml. Nu kan vi deploya servleten. web.xml placeras som vanligt i ORION_HOME/applications/servlet/WEB-INF förutsatt att vi angett i application.xml att path för webapplikationen har värdet ../applications/servlet. Filen SysOutServlet.class placeras i ORION_HOME/applications/servlet/WEB-INF/classes/ai1/serverjava/servlet om klassen, liksom i exemplet, ligger i paketet ai1.serverjava.servlet. Nu kan vi surfa till servleten på URL:en http://localhost:8080/servlet-example/servlet/SysOutServlet förutsatt att servern finns på localhost och lyssnar på port 8080. Två delar av URL:en förtjänar en närmare förklaring: servlet-example är webapplikationens rotkatalog, dvs det vi angett som värde för attributet root i default-web-site.xml. servlet kommer sig av att alla servlets hittas i URL:en i en "underkatalog" till webapplikationens rot som heter just servlet. Om SysOutServlet.class byts ut laddar Orion automatiskt den nya versionen vid nästa request.

Vi kan förenkla det lite genom att lägga till nedanstående rader i web.xml:
  <servlet-mapping>
<servlet-name>SysOutServlet</servlet-name>
<url-pattern>SysOutServlet/*</url-pattern>
</servlet-mapping>
Nu ser web.xml ut så här, web.xml.Det gör att alla URL:er som börjar med SysOutServlet kommer att hanteras av servleten vars <servlet-name> har värdet SysOutServlet, dvs av den servlet vi nyss deklarerade i web.xml. Vinsten med detta är att vi slipper katalogen servlet i URL:en, vi kan nu hitta vår servlet på http://localhost:8080/servlet-example/SysOutServlet

Att skicka ett request vidare från en servlet till en JSP (eller HTML-fil)

Enligt asvnittet Vad är en servlet och när ska de användas? ovan är det vanligt att en servlet skickar ett request vidare till en annan fil som utgör själva vyn. Att skicka ett request vidare på det viset görs i två steg:
  1. Skaffa ett objekt av javax.servlet.RequestDispatcher genom att anropa metoden getParameter() i javax.servlet.http.HttpServletRequest och skicka med en sträng som är den URL till vilken requestet ska vidarebefodras. URL:en kan anges absolut, den börjar då efter webapplikationens rotkatalog och dess första tecken ska vara en slash (/). Den kan också anges relativt den URL som browsern skickade requestet till, dess första tecken ska då inte vara en slash (/). 
  2. Anropa metoden forward() i RequestDispatchern och skicka med request- och response-objekten. Detta metodanrop betyder att svaret till browsern skickas från den URL som RequestDispatchern är kopplad till. Observera dock att metoden forward() sedan returnerar och att exekveringen av koden i servleten fortsätter.
Här kommer en servlet som skickar alla request vidare, ForwardingServlet.java och dess web.xml. Anropa den med URL:en http://localhost:8080/ForwardingServlet?newUrl=XXX Den kommer då att skicka det som finns på XXX till browsern. Om det till exempel finns en fil i webapplikationens rotkatalog som heter whatever.html kommer den att visas om vi surfar till http://localhost:8080/ForwardingServlet?newUrl=/whatever.html

En komplett webapplikation (number guess game)

Till sist tar vi en titt på en komplett webapplikation. Det är ett litet spel som går ut på att gissa rätt nummer. För att få spela det är det nödvändigt att logga in. Här finns hela applikationen, numguess.war. war (Web ARchive) är det format som används för att packa webapplikationer. Det är precis likadant som jar bortsett från att katalogen WEB-INF (som innehåller web.xml) finns med. Webapplikationen kan packas upp med kommandot jar xvf numguess.war.  Den uppackade katalogstrukturen går att deploya oförändrad. Förstasidan är filen index.jsp. Här finns källkoden för de ingående Java-klasserna, numguess-src.jar.

Till sist några kommentarer om applikationen:
Tyvärr följer Orion inte servlet-specifikationen när det gäller att spara information om vilken session användaren deltar i. Den använder nämligen inte cookies utan skriver i stället om url:erna. Detta ställer till problem, men genom att ändra i filen orion-web.xml som beskrivs i slutet av avsnittet Att exekvera en enkel JSP (hello world) ovan kan vi tvinga Orion att göra som den ska, dvs använda cookies. Vi måste lägga till taggen <session-tracking autoencode-urls="false"/>. Kom ihåg att servern måste startas om för att ändringen ska gälla. Min orion-web.xml ser ut så här, orion-web.xml.