Java Servlets er en glimrende idé, men blir det enklere å bruke Java-programmer på tjenersiden med Java Server Pages?
Trykket i Nettverk & Kommunikasjon nr.07/1999
(c) Anders Fongen 1999
Anders Fongen er høgskolelektor og fagsjef ved Den Polytekniske Høgskolen. Web-stedet hans er på www.fongen.no
"Java er arkitekturnøytralt", har vi latt oss fortelle, slik at vi kan skrive Java-programmer og distribuere dem over internet i form av "Applets". De fleste Web-lesere kan utføre slike applets. "Write once, run anywhere", sies det. Men har det vært så enkelt i praksis? Nei, det har det ikke; Java Applets har fått begrenset popularitet på grunn av ulikheter mellom Web-leserne, bare det å kjøre samme Applet på ulike versjoner av samme Web-leser har vist seg å volde visse problemer, noe som skyldes ulike versjoner og ulik kvalitet på det lokale Java- systemet.
Dette er egentlig en lekse vi burde ha lært for lenge siden: Kompleks teknologi skal ikke distribueres! Det som kan distribueres er enkel, vel forstått teknologi som f.eks. HTML/HTTP (Web hadde neppe blitt noen suksess dersom teknologien i bruk hadde vært mer komplisert). Det er derimot så mange som lever av å selge "desktop" kompleksitet som glemmer dette (eller som ikke vil huske det). De som derimot lever å selge "sentralisert" kompleksitet er derimot flinkere til å huske dette. Money talks.
Distribuerte anvendelser er altså mest vellykket når vi beholder de komplekse komponentene i tjeneren (f.eks. databaseteknologien) og lar klienten forholde seg kun til f.eks. HTTP-protokollen. Kompleksiteten blir dermed "skjult" for brukeren og brukerens programvare bak noe som ser ut som en vanlig Web-tjener. Dette er en vellykket og populær måte å lage distribuerte applikasjoner på. Vanlige programmeringsteknikker for å få dette til er f.eks. CGI-skript skrevet i Perl, eller ASP-skript skrevet i Visual Basic. Begge disse to metodene har derimot sine ulemper, og den etter vår mening mest interessante programmeringsteknikken er å lage såkalte servlets programmert i Java.
Vi har tidligere omtalt Servlet-teknologien (nr.1/1999), den har blant sine fordeler at den baserer seg på et utbredt og arkitekturnøytralt programmeringsspråk, og at den tar vare på såkalt "sesjonstilstand". Det siste innebærer at en Servlet er i stand til f.eks. å logge seg inn i en database ved én Web-transaksjon, og beholde den innloggede tilstanden også ved senere transaksjoner. Dette er ikke mulig ved programmering på CGI-vis, noe som krever en (kostbar og langsom) innlogging i databasen for hver eneste transaksjon.
Fordelen med Java Servlets ligger altså i kombinasjonen av å være arkitekturnøytral og å være "tilstandsorientert". Programmeringsmodellen for "Web-programmer" ligner forøvrig mye på hverandre, og det er ikke vanskelig å lage servlets dersom man har erfaring fra f.eks. CGI-skript.
Men allikevel er det en del å sette fingeren på ved denne programmeringsmodellen, enten det heter CGI eller Servlets: Nemlig det at programkoden som skrives er svært "program-orientert", i motsetning til vanlig HTML-kode, som er "dataorientert". Med dette mener vi at all html-kode som skal vises i webleseren må genereres av utskriftssetninger i Java. Det blir ganske tungvint i de tilfellene hvor det dreier seg om mye "fast" og lite "programmert" HTML-kode. Der hvor programmeringsbehovet er lite, blir en servlet et lite hensiktsmessig format, spesielt for dem som ikke har programmeringserfaring.
Begrepet server pages har vært brukt i litt ulike sammenhenger, særlig i form av Microsofts "Active Server Pages" (ASP), en programmeringsteknikk som først og fremst benyttes på Microsofts "Internet Information Server". I en "server page" legger vi ikke HTML-kode inn i utskriftssetninger, men tvert om legger programlogikken inn mellom "tagger". Logikken er forsåvidt enkel: Sideinnholdet sendes til Web-leseren fra toppen og nedover, og idet man kommer til en seksjon med programlogikk vil denne koden "utføres", og de resulterende utdata sendes til web-leseren. Programlogikken utføres i tjeneren, og Web-leseren vil aldri få se programsetningene på siden.
Man kan skifte mellom "tekst"- og "program"-modus mange ganger nedover i dokumentet, man kan dele en programløkke over flere program-biter slik at den HTML-teksten som står inne i løkken blir sendt til Web-leseren hver gang programløkken utføres (se listing 1). Tilsvarende er det enkelt å omslutte en tekst med en if-setning, slik at den bare vises dersom if-testen er "true".
Slik koding er lett å komme igang med, og den "ligner mer" på vanlig HTML- koding, noe som kan være en fordel for uerfarne programmerere, men denne blandingen av programlogikk og fast tekst blir fort ganske ufyselig å se på. Erfaringer med ASP er at en omfattende applikasjon skrevet som "server pages" krever en erfaren og klartenkt programmerer for at det hele ikke skal bli et kaos av programsetninger spredt ut over et stort antall små filer.
ASP er blitt populært, og i Java-miljøet har man lurt om en lignende idé kan gjøre det lettere å bruke Java på tjenersiden (lettere enn å lage Servlets). Vi har dermed fått JSP - "Java Server Pages". JSP er dette: Skriv HTML-koden inn i et dokument, skriv Java-koden i det samme dokumentet omsluttet av egne "tagger". Utskriften av programsetningene sendes til klienten på det stedet i datastrømmen hvor programsetningen var satt inn.
Det følger med noen eksempelfiler som viser noen vanlige applikasjoner, men de som vi ønsket å lage, fantes det ingen eksempler på. Vi ønsket å studere databasebruk gjennom alminnelig JDBC-programmering og hvordan vi kunne lagre sesjonstilstand og applikasjonstilstand (felles tilstand for alle brukerne) i tilknytning til databasebruk.
La det være sagt med én gang. Det å skrive Java-kode i form av JSP er noe annet enn å skrive Java-klasser i tradisjonell forstand. For å beholde en viss "visuell orden" er det nødvendig å holde de faste tekstdelene noenlunde separat fra Java- koden, og det er derfor lurt å ha Java utskriftssetninger samlet på et lite antall steder i koden. JSP lar deg "deklarere" kode og data på et separat sted på siden som er omsluttet av egne tags "<%! ... %>". Slik kode blir ikke utført "inline", men må kalles som funksjoner. Inne i slike deklarasjoner kan vi ikke blande inn HTML-kode slik som i "inline" javakode, men vi må bruke utskriftssetninger for å sende data til Web-leseren (og objektet det skrives ut gjennom må gis som en parameter til funksjonen, fordi dette objektet ellers ligger utenfor "scope").
En annen interessant strategi er at JSP underveis i denne oversettelsen også lager en XML-versjon av dokumentet. Spesifikasjonene for JSP sier tydelig fra hvordan denne XML-versjonen skal utformes, slik at en programmerer gjerne kan lage applikasjonen i XML-format. Nærværet av XML merker vi når vi skriver de norske tegnene "æøå" inn i JSP-siden. Under behandlingen av denne siden oppstår det en feilmelding om at disse tegnene ikke er tillatt, og derfor må vi erstatte dem med "riktige" ekvivalenter, nemlig æ, ø og å. Egentlig er dette et krav ved HTML også, men ingen Web-lesere har noensinne klaget på de norske tegnene.
Dette var noe av det første vi måtte lære oss: Finne frem til kildeteksten for Servleten, finne ut hva slags kompileringsfeil som var oppstått, og så finne ut hvor i JSP-teksten denne feilen hadde sitt utspring. Oversettelsen fra JSP til Servlet er ingen veldig komplisert prosess, så dette lot seg finne ut uten mye styr, men gjør dette JSP til en enklere programmeringsform enn å bruke Servlets? Neppe.
Gjennom request-objektet kan Java-koden finne ut hvem klienten er, hvilke URL- parametre som er brukt, og innholdet av felt i en HTML-form. Request-objektet er nytt for hver HTTP-transkasjon.
Gjennom session-objektet kan vi ta vare på opplysninger gjennom en kjede av transaksjoner mellom Web-tjeneren og en Web-leser. Web-tjeneren holder rede på de forskjellige Web-leserne gjennom å plante "cookies" i dem. Det er en mye sikrere løsning å la Web-leseren holde på opplysninger om f.eks. brukernavn og passord enn hva det er å la dem transporteres frem og tilbake mellom Web-tjener og Web-leser for hver HTTP-transkasjon. Det er dessuten nødvendig å holde på andre typer opplysninger mellom transaksjonene, som f.eks. innholdet i handlekurven, håndtak til åpne filer og databaseforbindelser. Session-objektet opphører å eksistere en viss tid etter siste transaksjon, standard konfigurasjon er 30 minutter.
Gjennom application-objektet kan vi spørre etter opplysninger som har med hele "applikasjonen" å gjøre, og vi kan i likhet med "session"-objektet lagre og gjenfinne opplysninger der. Med "applikasjon" menes alle Web-sidene på en katalog og alle dens underkataloger. Dersom en databaseforbindelse ikke skal gjøre forskjell på brukerne, men alle skal ha samme rettigheter, da er det en god idé å legge den i "application"-objektet, slik at alle brukerne deler på denne ressursen. Dette objektet forsvinner aldri, og kan også brukes til å lage en avansert "visitor counter".
Med Java er det ikke vanskelig å deklarere en sekvens av programsetninger om "synchronized", slik at andre transaksjoner må vente til den er ferdig. Det er derimot vanskeligere å finne ut hvor det er nødvendig å gjøre dette. Men kunnskap om dette er noe som hører moderne programmering til, så det er det bare å lære seg.
Med CGI-programmer og Servlets er det vanlig (men ikke obligatorisk) å legge programfilene på helt separate kataloger under Web-tjeneren, slik at vi kan legge strengere adgangsrestriksjoner på disse filene. Dette er også strategien som må velges når man bruker "server pages", nemlig at de må holdes helt adskilt fra "passive" HTML-sider. Kun bestemte kataloger skal inneholde server pages, og disse katalogene får en særlig skrivebeskyttelse.
Dette er en detalj som kan være fort gjort å overse når man konfigurerer en Web-tjener for første gang, og det er vanskelig å rette det opp når tjeneren er full av sammenlenkede HTML-sider. Resultatet kan være en Web-tjener som er forsvarsløs mot enhver uærlig, udugelig eller udisiplinert programmerer som velger å utstyre Web-sidene sine med JSP-basert programlogikk.
Dette er også av hensyn til stabiliteten av Web-tjeneren nødvendig å unngå dette. Det skal ikke mer til enn en vanlig programmeringsfeil, f.eks. en løkke som aldri avsluttes, før tjeneren ikke lenger klare å utføre sine vanlige oppgaver. Og jo slappere man er med tilgangen til å skrive tjenerprogrammer, jo oftere vil man oppleve slike problemer.
Overraskende nok: JRUN har ingen slik konfigurasjonsmulighet for å skille mellom kataloger med og uten JSP-tillatelse. Alle kataloger kan utstyres med JSP-dokumenter, noe som etter vår mening diskvalifiserer dette produktet for bruk i et større intranett.
Hvorvidt server pages i det hele tatt er en god idé er vi mer usikre på. Utvikling av Web-applikasjoner bør, som annen programutvikling, følge de reglene som har vist seg fornuftige, og det er å legge vekt på inndeling i klasser og moduler, redusere sideeffekter ved å unngå bruk av felles variabler, gjennomføre uavhengig testing på enkeltdelene før de "skrus sammen".
"Server pages" gir ingen god støtte til denne prosessen, og det kreves en erfaren og bevisst programmerer for å utvikle skikkelige Web-programmer. Forøvrig er jo en av Javas sterkeste sider at programmereren blir "tvunget" til å programmere objektorientert, i motsetning til ved utvikling i C++. Det som later til å være en åpning for at ikke-profesjonelle programmere skal lage Java-baserte Web- applikasjoner, kan være det motsatte: Nemlig at også de profesjonelle programmererne må ta seg ekstra godt sammen for at et utviklingsprosjekt basert på JSP skal bli vellykket.
Her kommer linjer med forskjellig skriftstørrelse:<br>
<% for(int i = 1;i < 8;i++) { %>
<FONT SIZE=<%=i%>>Hello World</FONT><BR>
<% } %>
Her er denne listen ferdig
<%
Connection conn;
try {
// Load the Oracle JDBC driver
Class.forName("oracle.jdbc.driver.OracleDriver");
} catch (ClassNotFoundException e2) {
e2.printStackTrace();
}
try {
if (session.getValue("dbConn") == null) {
%>
Vi oppretter en ny forbindelse...<br>
<%
conn = DriverManager.getConnection
("jdbc:oracle:thin:@oracle.dph.no:1521:xx", "stud12 ",
"passord");
// Lagre forbindelsen i "session"-objektet
session.putValue("dbConn",conn);
} else {
%>
Vi bruker en gammel forbindelse...<br>
<%
conn = (Connection)session.getValue("dbConn");
}
Statement stmt = conn.createStatement ();
ResultSet rset = stmt.executeQuery("select * from student");
// Vis resultatsettet
dumpResultSet(rset,out); // Ikke vist i dette eksemplet
rset.close();
stmt.close();
} catch (Exception e1) {
e1.printStackTrace(); // Vises bare i loggfilen
}