Java Server Pages

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.

JRUN - Web-tjener med JSP

Først må det sies at JSP er en ganske ny spesifikasjon, den er nettopp kommet i versjon 1.0, etter å ha eksistert som "beta" i en tid. Det eksisterer allikevel tre Web-tjenere som kan behandle JSP-sider, og det finnes "tilkoplingsmoduler" for mange av de mest kjente Web-tjenerne, f.eks. IIS og Apache. Den vi har brukt som utgangspunkt for noen eksperimenter er Jrun (www.jrun.com), som lar seg laste ned via Internet for en 30-dagers evalueringlisens. Jrun er forøvrig helt skrevet i Java, og skal etter sigende kunne kjøres på UNIX og Linux også. Vi har ikke forsøkt det, men kjørt Jrun under Windows95. Jrun er også en grei plattform for å kjøre Java servlets.

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").

Automatiske Servlets

En interessant strategi for JSP er at dokumentet oversettes til en Java servlet i kildetekstform, for så å kompileres til en klasse-fil. Dette gjøres automatisk hver gang klienten ber om en side som er blitt endret. Derfor vil den første forespørselen mot en ny eller endret JSP-side medføre lengre ventetid, fordi JSP-dokumentet må oversettes og kompileres først. Påfølgende forespørsler går derimot mye raskere.

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 &aelig;, &oslash; og &aring;. Egentlig er dette et krav ved HTML også, men ingen Web-lesere har noensinne klaget på de norske tegnene.

Pussige feilmeldinger

Etter ha skrevet en JSP-side, er det senere altså mulig å studere kildeteksten til den Servleten som er automatisk generert. Det er nyttig, muligens nødvendig å kjenne litt til hvordan en Servlet er laget og hvordan den virker, fordi mange feilmeldinger som oppstår i JRUN henviser til Servlet-teksten, ikke til JSP- teksten. Med andre ord: Skriver du en syntaksfeil i JSP-teksten, får du sjelden feilmelding fra oversettelsesfasen fra JSP til Servlet, men oftest fra kompilatoren som skal oversette Servlet-teksten til en Java-klasse. Disse feilmeldingene refererer til programsetninger som du ikke selv har skrevet, og til linjenummer som ikke sier deg noe som helst.

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.

Innebygde objekter

En hendig egenskap ved Java Server Pages er forekomsten av såkalte "intrinsic objects", objekter som kan refereres uten først å deklareres (ikke noe magi, bare det at deklarasjonen alltid er tilstede i Servlet kildetksten).

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".

Bruk av HTML-editorer

Enkelte HTML-editorer "kjenner igjen" ASP-kode i en HTML-side, og lar den være i fred, eller assisterer brukeren i å redigere den. Andre editorer vil insistere på å lage HTML-koder innimellom alt du selv skriver, og vil kunne klusse til Java-koden din grundig. Noen ganske få HTML-editorer kan utstyres med tilleggsmoduler for å bli "JSP-aware", men regn med at en "ren ASCII-editor" (som Notepad) blir verktøyet ditt om du vil lage JSP-sider.

Bruk av Java Beans

En interessant del av rammeverket rundt JSP er bruken av Java Beans. Java Beans er Java-klasser som er skrevet etter bestemte regler. F.eks. skal medlemsfunksjonene for å lese og skrive instansvariablene følge bestemte navneregler. JSP lar deg opprette Java Beans, og sette instansvariablene (kalt "properties") og kalle andre metoder på den. I tillegg kan man angi hva slags levetid en Bean skal ha, dvs. om den skal oppbevares i "session"- eller "application"-objektet. Vi har ikke gjort egne eksperimenter med Java Beans under JSP, men det fortoner seg som fordelaktig i forhold til bruk av eksterne Java-klasser forøvrig, fordi bruken av Java Beans er bedre "forankret" direkte i oppmerkingsspråket (egen tag som heter "usebean").

Threadsafe

Om man ønsker å manipulere et objekt som er delt mellom flere transaksjoner, f.eks. "application"-objektet, må man ta høyde for at det kan oppdateres av flere transaksjoner samtidig. Hva betyr det? Jo, at det kan oppstå såkalt "race condition" om ikke oppdateringen av objektet er beskyttet slik at en hel operasjon (en kritisk region) kan fullføres uten at andre kommer "innimellom" med sine operasjoner.

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.

Sikkerhet

Enhver som lager Web-applikasjoner kan opptre på vegne av Web-tjenerens "bruker", og bruke eller misbruke dennes rettigheter uhindret. Det kan innebære å endre loggfiler, endre status på andres transaksjoner o.l. I det hele tatt: Det å skrive Web-baserte programmer er en betrodd oppgave, og det er nødvendig å holde ubetrodde Web-redaktører (de som bare skal skrive HTML-sider) langt unna denne muligheten.

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.

Konklusjon

Microsofts "Active Server Pages" har vært en suksess, men bare 22% av verdens Web-tjenere kjører på Windows NT. Om de andre 78% ønsker et tilsvarende programmeringsmiljø, da trenger de noe annet. Og det får de med Java Server Pages.

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.

Listing 1: Variabelt innhold i "tagene"

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

Listing 2: Lagring av en JDBC-forbindelse i "session"-objektet

<%
    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
    }