Tomas Holt 05.02.2008 Opphavsrett: Forfatter og HiST/AITeL Lærestoffet er utviklet for faget LO701D Interaktive Webtjenester med Java og XML 1 Resymé: I denne leksjonen skal vi på grunnleggende prinsipper i (Java Server Pages).. Innhold 1...1 1.1 INNLEDNING...1 1.2 OG WEB-TJENER...1 1.3 ADMINISTRASJONSGRENSESNITTET TIL APPLIKASJONSTJENEREN... 3 5 1.3.1 Java Servlets...5 1.3.2 eller Servlet...7 1.3.3 Elementer i...9 1.1.1.1 Uttrykk...9 1.1.1.2 Scriptlets...10 1.3.4 Innebygde objekter i...11 1.1.1.3 Objektet out...11 1.1.1.4 Objektet request...12 1.1 Innledning Læreboken tar utgangspunkt i at du kan grunnleggende programmering med Java Servlets/. Det er derfor nødvendig å skrive litt om dette. Stoffet er i stor grad hentet leksjonen fra leksjoner i faget Webprogrammering med. 1.2 og web-tjener I dette faget skal vi bruke applikasjonstjeneren (du kan velge å bruke en annen web-tjener om du vil) som følger med J2EE/Java EE. Denne inneholder også en webtjener. Den enkleste måten å bruke denne tjeneren på er å gjøre som følger:
Lag deg en katalog med ønsket navn, heretter omtalt som katalog. I denne katalogen vil du legge -filene dine (forklaring på hvordan disse kan lages finner du i slutten av leksjonen). I tillegg må du opprette underkatalogene WEB-INF og WEB-INF/classes. Om du har egne java-klasser som skal brukes av 'ene så puttes disse i kompilert tilstand i classes-katalogen. Merk at klassene må ligge i pakker og at man vil få en underkatalog for hver del av pakkenavnet. Om vi f.eks. har en klasse som begynner slik (dvs. ligger i pakken webtjenester.leksjon2) package webtjenester.leksjon2; public class EnKlasse{} så kompileres klassen og legges i katalogen WEB-INF/classes/webtjenester/leksjon2. Når vi bruker applikasjonstjeneren er den enkleste måten å gjøre -filene tilgjengelig for klienter via manuelt å lage to xml-filer som legges i WEB-INF katalogen. Den første fila heter web.xml og skal se slik ut: <?xml version="1.0" encoding="utf-8"?> <web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/xmlschema-instance" xsi:schemalocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"> <display-name>test</display-name> <distributable/> </web-app> Denne fila kan du kopiere. Det eneste som er interessant å endre er «display-name» test. Dette navnet bør strengt tatt være beskrivende for applikasjonen, men er helt valgfritt. Den andre fila skal hete sun-web.xml. Denne fila brukes for å fortelle hvilken adresse man må skrive i nettleseren for å finne web-applikasjonen. Fila kan se slik ut: <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE sun-web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Application Server 8.1 Servlet 2.4//EN" "http://www.sun.com/software/appserver/dtds/sun-web-app_2_4-1.dtd"> <sun-web-app> <context-root>/test</context-root> </sun-web-app> Det som er interessant å endre her er «context-root». I denne fila oppgir vi at applikasjonen nås med å skrive http://localhost:8080/test i nettleseren. Nå har du to måter å få lagt webapplikasjonen ut på tjeneren (begge forutsetter at du har laget riktig katalogsturktur, med nødvendige filer som beskrevet over). Den enkleste er som følger: asadmin deploydir fullstitilkatalog Dette fungerer helt fint om du ikke trenger å oppgi brukernavn til tjeneren (konfigurert ved installasjon). Om dette er nødvendig så blir kommandoen asadmin deploydir --user navnpåadminbruker fullstitilkatalog Du vil så måtte skrive passord. Du vil så få meldingen Command deploydir executed successfully side 2 av 14
Metode nummer to er å lage en WAR-fil. Dette gjør du med å stille deg inne i katalogen katalog. Skriv så jar cfv navnpåwar.war * WAR-fila kopieres nå til autodeploy-katalogen til applikasjonstjeneren. Denne katalogen finner du der hvor du installerte J2EE/domains/domain1/autodeploy. Når dette er gjort kan du teste applikasjonen ved å åpne nettleseren å skrive http://localhost:8080/test. Det finnes muligheter for å automatisere denne prosessen. Ant-skript er en mulighet. Du finner denne muligheten nærmere beskrevet i skrivet Intro til J2EE-tutorial'en. Får du problemer så bør du se i loggen til applikasjonstjeneren (se neste kapittel). Det er her alle feilmeldingene kommer. System.out.println() setninger i 'ene vil også havne her (fint for debugging). 1.3 Administrasjonsgrensesnittet til applikasjonstjeneren Applikasjonstjeneren har et eget web-basert administrasjonsgrensesnitt. Dette kan brukes ved å taste inn følgende url; http://localhost:4848. Tast inn brukernavn og passord (angitt under installasjonen). Administrasjonsgrensesnitt vil nå dukke opp. Figuren under viser hvordan dette ser ut. I dette grensesnittet kan vi konfigurere diverse ting som har med tjeneren å gjøre. Vi kan også fjerne web-applikasjoner som ikke skal kjøre osv. Mer detaljerte opplysninger kan man også få med å velge ønsket applikasjon i menyen på venstre side. En funksjon som fort kan bli nyttig er visning av loggen til tjeneren. Dette er nyttig når ting ikke går helt som planlagt. Velg Common Tasks > Search Log Files. Her kan du søke i ulike side 3 av 14
nivåer, dvs. viktighetsgrad av meldingen. Vanligvis så er info greit. Trykk search og du vil få se alle meldinger fra tjeneren. De siste vil være øverst. For hvert innslag i loggen kan du også få mer detaljerte opplysninger ved å trykke på lenken details i oversikten. side 4 av 14
Det tas utgangspunkt i at dere vet hva hvordan websider fungerer med en klient og en tjener, og videre at dere vet at det trengs tjener-side script for å få dynamiske websider. Hvis du har behov for å lese om dette først, anbefaler jeg deg å først se på leksjon 1 fra faget Webprogrammering med som er lagt ut sammen med denne leksjonen. Vi ønsker nå å se på hvordan vi kan bruke enkel for å få til dynamiske websider. Vi kommer bare til å se på de enkleste delene, som vi mener dere trenger for å gjøre øvinger og prosjekt. Før vi begynner med vil vi se på Java Servlets som er et sentralt tema i webprogrammering med. 1.3.1 Java Servlets er sterkt knyttet til Java Servlets, fordi en automatisk vil oversettes til en servlet før den kjøres. En servlet har mange likhetstrekk med en Java applet. I begge tilfellene lages en.java fil. Denne filen vil så kompileres og vi får en.class fil. Denne filen består av kjørbar kode. import javax.servlet.*; public class MinServlet implements Servlet De to linjene over er hentet fra en servlet. Koden implements Servlet forteller at dette er en servlet. Servlet er et interface, og alle klasser som implementerer dette vil være en servlet. En servlet er beregnet på å tjene klienter, andre klasser og til og med andre servlet er. Fordi vi konsentrerer oss om web vil servlet en fungere i samarbeid med webtjeneren. Det er webtjeneren som sørger for å ta i mot forespørsler, og disse vil sendes videre til servlet en. Servlet en sørger for å gjøre noe og sende svar tilbake. Servlet en vil derfor opptre som en del av tjeneren. Før webtjeneren kan sende forespørsler til en servlet, må den aktuelle servlet en instansieres (lages) og initialiseres. Web-tjeneren vil instansiere en servlet på samme måte som hvilken som helst annen klasse. side 5 av 14
Figur 2: Servlet livsyklus Figuren over beskriver hvordan livssyklusen til en servlet er. Merk at fordi MinServlet implementerer interface et Servlet er webtjeneren garantert at den kan kalle metodene init(), service() og destroy() (de andre metodene er ikke vist her av hensyn til oversikten). Se også at web-tjeneren oppretter et objekt av typen MinServlet men referansen min som refererer til dette objektet er av type Servlet, og vi kan dermed kun kalle de metodene som er beskrevet i interface et Servlet. Dette er en god løsning fordi webtjeneren ikke er interessert hvordan servlet ene er implementert, men kun hvordan servlet ene kan lages, initialiseres, kjøres og fjernes. Web-tjeneren kan derfor behandle alle servlet er likt. Når en lager en servlet beregnet på web vil det imidlertid være hensiktsmessig å arve fra klassen HttpServlet i stedet for å implementere Servlet interface et. HttpServlet er en (abstrakt) klasse som implementerer Servlet interface et. Denne klassen spesifiserer en del metoder som er veldig kjekke å ha når man jobber mot web. Særlig viktige metoder i HttpServlet er: - public void init() throws ServletException - public void service(httpservletrequest req, ServletResponse res) - public void doget(httpservletrequest req, ServletResponse res) - public void dopost(httpservletrequest req, ServletResponse res) Metodene doget() og dopost() er metoder som gjør det enkelt å ta i mot informasjon fra HTML-skjema. Her er et script som viser klokken (klokke.jsp): <HEAD><TITLE> Min første side </TITLE></HEAD> <H1> Klokka er: <%= new java.util.date() %> </H1> En servlet som gjør det samme kan se slik ut: import javax.servlet.http.*; side 6 av 14
import javax.servlet.*; import java.io.*; import java.util.date; public class KlokkeServlet extends HttpServlet{ public void service(httpservletrequest req, HttpServletResponse res) throws IOException, ServletException{ res.setcontenttype("text/html"); PrintWriter out = res.getwriter(); out.println(""); out.println("<head>"); out.println("<title> Min første Servlet </TITLE>"); out.println("</head>"); out.println(""); out.println("<h1> Denne servlet'en viser klokka nå: </H1>"); Date klokke = new Date(); out.println(klokke); out.println(""); out.println(""); } } Koden extends HttpServlet angir at klassen arver fra klassen HttpServlet. Som vi ser implementerer denne klassen kun en metode. De andre metodene som er spesifisert i interface et Servlet er implementert i klassen som vi arver fra, nemlig HttpServlet. Som vi ser blir det mindre kode når vi gjør dette med. Den blir også mindre kryptisk. I -koden blandes HTML-kode og script-kode i skjønn forening, mens servlet en kun inneholder javakode. All HTML-kode som skal sendes tilbake til klienten fra servlet en sendes med out.println(). Dette gjør at vi fort kan miste oversikten over HTML-koden i servlet en. Vi ser også at vi er nødt til å passe på at koden kaster de riktige unntakene. Kommandoen res.setcontenttype( text/html ) sørger for å oppgi hvilken type respons denne servlet en gir. Når det er snakk om HTML må denne være satt på denne måten. Dette fordi nettleserne oppfører seg ulikt når denne ikke er satt. Hvis ikke denne er satt vil Netscape tolke innholdet som ren tekst (i stedet for HTML) noe som gjør at websiden ikke blir som vi hadde tenkt. Vi ser også at vi må lage oss vår egen PrintWriter for å kunne sende respons tilbake til klienten. Konklusjonen er at vi kan gjøre det samme med og servlet er, men at skjuler en del av kompleksiteten for oss. Særlig gjør det enkelt å jobbe med HTML-kode! 1.3.2 eller Servlet Vi har nå sett at forskjellen mellom servlet er og ligger i hva vi som utvikler er nødt til å gjøre. Særlig i de tilfellene det er snakk om presentasjon på websider vil være å foretrekke framfor servlet er. Grunnen til dette at vi på en enkel måte kan flette HTML-kode sammen med programkode. Dette er mye mer tungvindt med en servlet. Imidlertid så kan forholdet snu seg når det er snakk om å lage mye kode og lite presentasjon. Da vil fort oppleves som tungvindt, fordi vi ikke kan programmere på vanlig måte. I slike tilfeller vil det være mer hensiktsmessig med en servlet enn. Er dermed ubrukbar for litt mer avanserte websider? Nei. I har vi muligheten til å importere og bruke java-klasser og metoder i disse klassene direkte. Dette gjør at vi kan bruke til den delen som har med presentasjon på websiden å gjøre, mens vi kan ha egne javaklasser i de delene der det er snakk om mye programmering. Dette gjør at vi faktisk får en veldig god deling mellom presentasjon og logikk. Vi kan dermed lage presentasjonen side 7 av 14
(HTML-koden) for seg og logikken (programmeringskoden) for seg. Dette er særlig gunstig i større prosjekter der man vil ha profesjonelle designere som lager presentasjonen og profesjonelle programmerere som tar seg av programmeringen. De trenger da ikke å ta hensyn til hverandre. Sammensyingen av systemet gjøres ved at en legger inn en del kode sammen med HTML-koden som gjør at en får tak i klassene og metodene som programmererne har laget. gir muligheten til en god deling mellom presentasjon og logikk. side 8 av 14
1.3.3 Elementer i Her følger en tabell over de ulike elementene i : -element Kode Kommentar HTML-kommentar <!-- kommentar --> Kommentarer som sendes til nettleser sammen med resten av HTML-koden -kommentar <%-- kommentar --%> Kommentar som ikke sendes til nettleser Direktiv <%@page xxx %> f.eks. <%@page import= java.util.date() %> Denne kan få en til å oppføre seg på en spesiell måte. import er en av flere muligheter for å importere java-klasser som skal brukes i jsp-koden Uttrykk <%= uttrykk %> Skriver ut verdier til nettleseren Scriptlet <% java-kode %> Utfører java-kode Alle elementene over plasseres direkte inn i HTML-koden. Se f.eks. -koden tidligere i leksjonen der uttrykk brukes for å skrive ut dato og klokkeslett. Merk at det ikke er mulig å nøste -elementer. Det medfører at du f.eks. ikke kan ha uttrykk inne i en scriptlet. Vi vil nå se nærmere på uttrykk og scriptlet. 1.1.1.1 Uttrykk Vi har allerede brukt denne type -element i tidligere eksempel. Uttrykk starter med <%= og slutter med %>. Linjen <H1> Klokka er: <%= new java.util.date() %> </H1> fra klokke.jsp er et slikt uttrykk. Resultatet av linjen over var at klokken og datoen ble skrevet ut. Uttrykk fungerer også for primitive datatyper. F.eks. vil <%= 2+2 %> sørge for at 4 blir sendt til nettleseren. Merk deg at det ikke skal være semikolon til slutt i uttrykk. Du vil da få feilmelding (og ikke en direkte selvforklarende en)! side 9 av 14
1.1.1.2 Scriptlets En scriptlet er et element som starter med <% og slutter med %>. Det som settes inn i en slik scriptlet er java-kode. Vi vil derfor kontrollere hva som skjer i vår med scriptlet er. Vi kan bruke alle typer kontrollstrukturer, objekter osv. som vi er vant med fra java (vi kan også bruke vanlige java-kommentarer med // ): <% for (int i=0; i < 2; i++) { int j=i;} %> Hva vises i klienten i dette tilfellet? Ingenting. Det som skjer i scriptlet en i eksempelet er at vi går igjennom for-løkka og endrer verdien til j, men dette sendes ikke til nettleseren. Vi kan imidlertid bruke scriptlets til å sørge for output til nettleseren. Dette kan vi f.eks. gjøre ved å kontrollere HTML-koden. <% for (int i=0; i < 2; i++) { %> <H1> If I were a </H1> <% } %> RICH MAN Figuren under viser resultatet. Vi ser at linjen <H1> If I were a </H1> blir utført to ganger! Dette skjer pga. av de to scriptlet ene vi har. Vi ser at alt i mellom de to klammeparantesene { og } blir utført som en del av for-løkka! Scriptlet er brukes derfor til å styre det som skjer i en. side 10 av 14
1.3.4 Innebygde objekter i Følgende objekter er ferdigdefinerte objekter i : - request - response - session - config - out - pagecontext - application - page Disse objektene er direkte tilgjengelig for oss i en. Hvis jeg f.eks. vil bruke out-objektet så kan jeg gjøre det direkte i en scriptlet, slik: <% out.println( bla bla ); %> Vi vil nå se på to av disse, nemlig out og request. 1.1.1.3 Objektet out Vi har så langt brukt -elementet uttrykk (<%= og %>) for å sende dynamisk innhold til nettleseren. Vi har sett hvordan en blir omformet til en servlet. I servlet en blir det opprettet et out-objekt med følgende java-kode: PrintWriter out = res.getwriter(); Etter at vi har opprettet out-objektet kan det brukes til å sende HTML-kode til nettleseren. I er det et ferdigdefinert out objekt som tilsvarer out-objektet som lages i servlet en. Dette objektet er av klassen javax.servlet.jsp.writer. Denne klassen har mange metoder (se API-dokumentasjonen). Den metoden vi vil bruke mest er nok println(). Denne metoden gjør omtrent det samme som et uttrykk. Under vises et eksempel der begge disse måtene er brukt. Hvilket av eksemplene er mest oversiktlig? Eksempel med out <% boolean sann = true; String streng = sann... ; if (sann) out.println(streng); else out.println( usann.. ); %> Eksempel med uttrykk <% boolean sann = true; String streng = sann... ; if (sann) { %> <%= streng %> <% } else { %> <%= usann.. %> <% } %> side 11 av 14
Eksemplene over bør kunne gi en indikasjon på at det er tilfeller der det er greiere å bruke out-objektet i stedet for uttrykk. Merk at begge disse metodene vil sørge for å sende noe til klienten. Vi kan derfor godt ha HTML-kode i en out.println()-kommando eller inne i uttrykk. <% out.println( ); %> <H2> her blander vi litt </H2> <%= %> Resultatet vil for klienten se slik ut: <H2> her blander vi litt </H2> Du vil etter hvert gjøre deg opp en mening om når det er best å bruke HTML-kode, out.println() eller uttrykk. 1.1.1.4 Objektet request Request-objektet er et viktig objekt, da det er gjennom dette objektet vi får tilgang til informasjon fra klienten. Informasjonen kan være om klienten (f.eks. hvilken nettleser som brukes) eller det kan være informasjon som er gitt av brukeren (f.eks. via HTML-skjema). Request-objektet som vi har tilgang til implementerer interface et javax.servlet.http.httpservletrequest. Vi kan derfor finne ut hvilke metoder dette objektet tilbyr gjennom API-dokumentasjonen på http://java.sun.com/products/servlet/2.2/javadoc/index.html Vi vil nå se på de viktigste metodene som Request-objektet gir oss. getparameter() Den mest interessante metoden vil være getparameter(). Denne tar en streng som argument. Denne strengen vil være lik navnet på det HTML-elementet som tok imot opplysningene. HTML-elementet kan være slik: <INPUT TYPE= text NAME= mitttekstfelt > Opplysningene fra HTML-elementet over får vi tak i ved å bruke denne koden i en vår: <%= request.getparameter( mitttekstfelt ) %> Merk at getparameter() returnerer en streng (String) eller evt. null dersom ikke parameternavnet finnes. Dette gjør at når vi forventer oss et tall så må strengen omformes til et tall. side 12 av 14
getparameternames() Metoden getparameternames() returnerer en java.lang.enumeration som inneholder alle parameternavnene fra klienten. Hver parameter er representert som en streng i denne Enumeration. Dette gir oss mulighet til å gjennomsøke alle parameternavnene fra klienten på en enkel måte. Under vises koden for hvordan alle parameternavn og tilhørende verdi skrives ut (uansett hvor mange parametere som kommer fra klienten). <%@ page import="java.util.enumeration" %> <% Enumeration opplysninger = request.getparameternames(); while(opplysninger.hasmoreelements()){ String parameternavn = (String)opplysninger.nextElement(); String parameterverdi = request.getparameter(parameternavn); out.println("parameternavn: " + parameternavn +" "); out.println("parameterverdi: " + parameterverdi + "<BR>"); } %> Det som er greit med koden over er at vi ikke på forhånd trenger å vite navnet på parametrene som kommer fra klienten. I forrige delkapittel fikk vi tak i verdien til HTML-elementet mitttekstfelt ved å oppgi navnet på dette feltet i metoden getparameter(). Vi forutsatte da at vi visste navnet på mitttekstfelt på tjenersiden. I koden over henter vi ut parameternavnene med request.getparameternames(). Så henter vi verdien forbundet med hvert enkelt parameternavn (f.eks. verdien i feltet mitttekstfelt) med getparameter() på samme måte som i forrige eksempel. Vanligvis vil vi selvsagt vite hva slags felter vi kan forvente oss. Metoden getparameternames() er imidlertid nyttig i de tilfellene der vi vil skrive ut alle opplysningene som brukeren har gitt via et HTML-skjema. Dette kan være for å opplyse brukeren om hvilke valg han har gjort, eller det kan være i forbindelse med feilfinning. getparametervalues() Til nå har vi antatt at det kun har vært en verdi forbundet med et element i en HTML-skjema. Dette er imidlertid ikke bestandig tilfelle. Noen HTML-elementer har mulighet for flere verdier samtidig. Tenk bare på valgbokser (checkbox) der du kan krysse av for flere valg samtidig. side 13 av 14
Koden vil være som følger: <H2> Kryss av det som passer for deg </H2> <FORM ACTION="http://localhost/examples/mine/leksjon4/getParameterValues.jsp"> <INPUT TYPE="checkbox" NAME="beskrivelse" VALUE="lang"> Lang <INPUT TYPE="checkbox" NAME="beskrivelse" VALUE="kort"> Kort <INPUT TYPE="checkbox" NAME="beskrivelse" VALUE="tynn"> Tynn <INPUT TYPE="checkbox" NAME="beskrivelse" VALUE="tjukk"> Tjukk <INPUT TYPE="checkbox" NAME="beskrivelse" VALUE="smart" > Smart <INPUT TYPE="checkbox" NAME="beskrivelse" VALUE="dum"> Dum <BR> <INPUT TYPE="submit" VALUE="ok"> </FORM> Vi ser av koden at alle valgboksene har samme navn (NAME= beskrivelse ). Fordi vi kan krysse av flere valgbokser samtidig (f.eks. Lang og Tynn), vil vi ikke kunne bruke koden request.getparameter(). Hvis vi gjør det så vil vi kun få verdien til det første feltet (Lang). Vi er imidlertid også interessert i resten av verdiene. Det vi gjør da er å bruke metoden getparametervalues(). Vi bruker parameternavnet som parameter til denne metoden. Metoden returnerer en tabell med strenger som inneholder verdiene. <UL> <!-- lager en punktliste --> <% String[] verdier = request.getparametervalues("beskrivelse"); // sjekker om det er noen verdier tilgjengelig if (verdier!= null) { for (int i=0; i < verdier.length; i++){ out.println("<li>" + verdier[i]); } } %> Metoden getparametervalues() returnerer null hvis det ikke er noen verdier for det aktuelle parameternavnet. Legg merke til if-setningen i koden som sjekker om resultatet er null. Hvis vi ikke gjør denne sjekken vil vi få nullpointer exception i verdier.length hvis verdier == null (vi kan ikke kalle en metode på et objekt som ikke finnes). Under vises resultatet av -koden over når HTML-skjemaet er som vist figuren over. side 14 av 14