Python og CGI
Oppkopling
Når jeg starter SecureShell får jeg opp et vindu og jeg velger Quick Connect. Dialogboksen som skal fylles inn for å få kontakt ser slik ut:
![ssh ssh](sshconnect.gif)
Host Name er den serveren du vil kjøre mot, f.eks. brage.hiof.no
User Name er det brukernavnet du har på serveren, f.eks. borres
Som Authentication Method velger du Password. Når dette er klart trykker du
Connect og svarer med riktig passord når du blir spurt.
Med mitt utgangspunkt trenger jeg å være koplet opp mot serveren via Secure Shell av to årsaker. For det første vil jeg kontrollere og sette riktige rettighter til de filene jeg skal arbeide med. For det andre kan jeg bruke det vinduet jeg har åpnet mot serveren til debugging.
Rettigheter
Sett rettighetene til 755
chmod 755 minfil.py
Det vil si at vi gir rettighetene: rwx r-x r-x. Alle kan lese og kjøre programmer, mens bare du kan skrive.
Dersom vi gir færre rettigheter får vi problemer med å få til den ønskede funksjonaliteten, og dersom vi gir flere rettigheter er det mulig at serveren protesterer. Grunnen til dette er at det kan være lagt inn sikkerhetskontroll så vi ikke åpner for at hvem som helst kan endre våre filer. Scriptet vårt nekter å kjøre, uten at vi får noen instruktiv feilmelding.
De som bruker UltraEdit under Windows kan støte på et lite problem. Dersom du har satt opp UE til å lage back-up filer, kan du oppleve at rettighetene ødelegges når du sparer fila. Du kan unngå dette ved enten å skru av back-up eller ved å legge til en linje i fila: winnt\uedit32.ini:
DontUseRename=1
Debugging
Siden scriptet vårt kjører på serveren, har vi ikke slik kontroll over hvor feilmeldinger havner som når vi kjører på vår egen maskin. Jeg pleier derfor å teste ut så mye som mulig av scriptet på min maskin før jeg laster det over på serveren.
Det er to måter å debugge serverskript i Python:
-
Lese feilrapport fra serveren. Når serveren har problemer med å utføre en HTTP-forespørsel (som å kjøre et skript) lages det en feilrapport. Denne feilrapporten kan inneholde tekst som er generert fra serveren selv eller fra skripttolkningen i Python. Vi kan åpne et vindu i SSH og be om å få se denne rapporteringen fortløpende.Kommandoen som gir oss denne rapporteringen avhenger av hvordan serveren er satt opp. Systemansvarlige skal kunne svare på det. Eksempler som angir hvordan en slik kommando under Linux kan være:
tail -f /var/log/httpd/error_log tail -f /var/log/apache/error.log tail -f /var/log/apache2/error.log tail -f /var/log/httpd/error_log | grep ml150
Den siste avgenser rapporteringen til rapporter som gjelder bruker ml150 -
Skru på "CGI Trace Back" i Python. Skriv følgende linje i starten av skriptet:
import cgitb; cgitb.enable()
Merk at det er ikke alle feil som traces. Alle feil som skyldes syntaksfeil som oppdages før skriptet startes rapporteres som server-feil, dvs. server har ikke klart å starte skriptet.Skru av Trace Back, fjern linja som oppgitt over, nå skriptet er ferdig testet og skal i produksjon.
CR-LF
Tekstfiler på Unix/Linux lagres med bare linjeskift(LF), mens Windows normalt lagrer filene med "vognretur" og linjeskift (CRLF). Normalt, f.eks. i nettlesere, skaper ikke dette noe problem siden de fleste programmer justerer for denne forskjellen. Shellet på Linux som skal tolke vår fil før den starter Python liker ikke CRLF i det hele tatt. Det vil si at vi må passe på at de filene vi legger ut som CGI-script på en Linux-server følger Linux standarden og bare har LF. Hvis vi editerer med Linuxverktøy på Linux er ikke dette noe problem, men hvis vi editerer på Windows med Windowsverktøy må vi ta våre forholdsregler. Vi må ha en editor som legger fra seg filene slik Linux liker dem samtidig som de må vise dem fram på en fornuftig måte mens vi editerer.
Det er mange måter å ordne dette på. Jeg bruker valigvis NotePad++.
NotePad++ kan transformere filer mellom Windows og Unix format, se menyen Edit/EOL-Conversion (menyen Format i gamle versjoner av NP++). NotePad++ kan settes opp slik at den alltid skriver filer tilbake i den formen de ble åpnet eller slik at den alltid konverterer filer som åpnes.
Dersom en fil først har fått rett format, kan du åpne den i Idle som beholder formatet.
Eksempel 1
Et minimumseksempel på et CGI-script kan være slik:
#! /usr/bin/python print 'Content-type: text/plain\n' print 'hello'
Den første linja angir at denne file skal leses og kjøres av Python som er plassert slik stien angir. Dette er en konvensjon i Unix/Linux. Filer som ligger på cgi-bin er kandidater for å eksekveres, og Linux må finne ut hvem som skal gjøre det. Når vi skriver python uten å spesifisere hvilken Python versjon vi er ute etter får vi det som er default versjon på serveren. Dette vil variere litt etter som serveren oppgraderes. Merk i denne sammenhengen at siste versjon som er lansert av Python er 3.0. Denne er ikke bakover kompatibel og er ikke installert på den serveren jeg har som referanse. Hvis serveren har implementert flere versjoner av Python kan vi angi dette, f.eks. slik:
#! /usr/bin/python2.5
Den andre linja er obligatorisk i alle cgi-script og angir hva som kommer for det programmet som skal motta resultatet. Dersom vi skal skrive HTML-kode bør vi skrive:
print 'Content-type: text/html\n'
Nettlesere har variabel følsomhet for dette. Noen tolker html selv om vi angir text/plain.
Den tredje linja er din melding til verden. Vi ser at det er ikke noen koplett HML-fil, men siden nettleserne er så tilgivende vil de fleste presentere resultatet.
Gi denne fila et navn, f.eks.: mincgi.py og legg den ut på ditt serverområde under katalog cgi-bin. Lag denne katalogen hvis du ikke har den. Sett rettigheter som vist ovenfor. Test den ved å skrive URL'en i adressefeltet i en nettleser. Adressen kan være slik for bruker ml150:
http://www.ia-stud.hiof.no/~ml150/cgi-bin/mincgi.py
eller den kan være slik:
http://frigg.hiof.no/ml150/cgi-bin/mincgi.py.
Sjekk med systemansvarlig i de omgivelsene du jobber i.
Merk også at vi kan, og bør, kontrollere at det som skrives tilbake fra tjeneren er kodet på den måten vi ønsker. Vi kan kontrollere dette ved å skrive den første linja f.eks. slik:
print 'Content-type: text/html; charset=iso-8859-1\n'
eller slik:
print 'Content-type: text/html; charset=utf-8\n'
Eksempel 2
I dette eksempelet kopler vi et script opp mot en HTML-side der vi skal fylle inn noen data og sende dem til scriptet. Vi lager en minimalistisk HTML-fil for enkelhetens skyld, merk at adressen til pythonscriptet er satt opp for bruker ml150 på serveren ml:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"?/> </head> <body> <form action="http://www.ia.hiof.no/~borres/cgi-bin/demo/min2cgi.py" method="POST"> <p>Navn: <input type="text" name="navn" /> </p> <p>Adresse: <input type="text" name="adresse" /> </p> <input type="submit" value="Send inn"> </form> </html>
Vi lager mottagerscriptet slik:
#! /usr/bin/python import cgi form=cgi.FieldStorage() print 'Content-type: text/html; charset=iso-8859-1\n' print form navn='' adresse='' try: navn=form['navn'].value except: navn='ukjent' try: adresse=form['adresse'].value except: adresse='ukjent' print "Dette er det jeg fant: Navn: %s, Adresse: %s"%(navn,adresse)
Vi ser at her har vi importert modulen cgi. Det gjøre det mulig for oss å få input dataene, parametrene, til scriptet ordnet i et FieldStorage som i all hovedsak oppfører seg som et Python dictionary (form=cgi.FieldStorage()). Det første vi gjør er å skrive denne rett ut. Etterpå sjekker vi hva vi faktisk har fått inn og lager en litt mer menneskevennlig utskrift.
Eksempel 3
Dette eksempelet gjør det samme som eksempel 2, bortsett fra at det produserer en full HTML-fil som returneres. Koden nedenfor er derfor et alternativ til scriptet over.
#! /usr/bin/python import cgi print 'Content-type: text/html; charset=iso-8859-1\n' # preparing the content in body-element form=cgi.FieldStorage() navn='' adresse='' try: navn=form['navn'].value except: navn='ukjent' try: adresse=form['adresse'].value except: adresse='ukjent' # preparing the complete HTML-string S="""<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <META http-equiv="Content-Type" content="text/html; charset=iso-8859-1\"> <title>navn og adresse</title> </head> <body> <h2>Dette er det jeg fant</h2> <p>Navn: %s</p> <p>Adresse: %s</p> </body> </html> """ S=S%(navn,adresse) print S
Eksempel 4
Dette eksempelet rapporterer alle omgivelsen til Python på tjeneren. Det er altså denne informasjonen vi kan benytte oss av når vi skriver CGI-skript, i tillegg til parametere og cookies.
#! /usr/bin/python print 'Content-type: text/html; charset=iso-8859-1\n' S="""<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <META http-equiv="Content-Type" content="text/html; charset=iso-8859-1\"> <title>version</title> </head> <body> <h2>Python version:%s</h2> %s </body> </html> """ T="<pre>\n" import os, sys from cgi import escape keys = os.environ.keys() keys.sort() for k in keys: T=T+"%s\t%s\n" % (escape(k), escape(os.environ[k])) T=T+ "</pre>" print S%(sys.version,T)
Eksempel 5
Dette scriptet rapporterer input fra en form. Det kan brukes som et generelt testscript for å sjekke at det du sender fra en form kommer fram slik du har planlagt.
#! /usr/bin/python """ leser alle parametere og skriver dem tilbake innpakket i en enkel HTML-side """ import cgi import cgitb; cgitb.enable() HTML_PAGE="""<!DOCTYPE HTML> <html> <head> <title>std</title> <meta charset="UTF-8"/> </head> <body><h2>Dette fant jeg</h2> <hr/> <pre>%s</pre> <hr/> <div><a href="javascript:history.back()">Tilbake</a></div> </body> </html> """ form=cgi.FieldStorage() print 'Content-type: text/html\n' result='' for key in form.keys(): val=form.getvalue(key,'') if isinstance(val,list): # multiple selections in checkbox s='' for v in val: s=s+','+str(v) result+=key+':'+s[1:]+'\n' else: result+=key+': '+form[key].value+'\n' if len(result)==0: result='ingenting' print HTML_PAGE%result