Python
CGI
Python
Børre Stenseth

Python og CGI

Hva

Utgangspunktet er en situasjon der jeg som Windowsbruker som skal lage CGI-script på en Linux server. Det er et par ting som er verdt å merke seg. Det gjelder formen på selve scriptet, debugging og manipulasjon av rettigheter til de filene vi lage på tjeneren.

Jeg forutsetter at jeg kan nå filene på tjeneren på to måter.

  1. Jeg kan se og manipulere kataloger og filer via File Explorer. Det vil si at Linux serveren er mappet opp mot min maskins filsystem via Samba, og at mitt område på serveren er blitt tildelt en bokstav som identifikator.
  2. Jeg kan åpne et "Linux-vindu" mot serveren ved hjelp av PuTTy [1] eller et annet program [2] .

Oppkopling

Når jeg starter f.eks. putty må jeg Hostname og etter hvert må jeg oppgi navn og passord.

Host Name er den serveren du vil kjøre mot, f.eks. borres.hiof.no
User Name er det brukernavnet du har på serveren, f.eks. borres

Med mitt utgangspunkt trenger jeg å være koplet opp mot serveren 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.

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 Pythons Idle-shell, som beholder formatet.

cgi-bin

En av de tingene som er en del av server-konfigurasjonen er hvor eksekverbare filer skal plasseres. En vanlig løsning er at de skal plasseres i html/cgi-bin katalogen. det trenger ikke være slik, og i mine omgivelser er det ikke slik. Python-filene kan plasseres hvor som helst i html-katalogen.

Eksempel 1

Et minimumseksempel på et CGI-script kan være slik:

#! /usr/bin/python3
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. 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. Sett rettigheter som vist ovenfor. Test den ved å skrive URL'en i adressefeltet i en nettleser.

https://borres.hiof.no/wep/python/cgi/mincgi.py

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

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"/>
<title>test2</title>
</head>
<body>
<form action="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/python3
import cgi
form=cgi.FieldStorage()
print ('Content-type: text/html; charset=utf-8\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.

Du kan teste her https://borres.hiof.no/wep/python/cgi/testmin2.html

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/python3
import cgi
print ('Content-type: text/html; charset=iso-utf-8\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>
<html>
<head>
<meta charset="UTF-8"/>
<title>test3</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)
Du kan teste her https://borres.hiof.no/wep/python/cgi/testmin2-bs.html

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/python3
print ('Content-type: text/html; charset=utf-8\n')
S="""<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"/>
<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()
for k in keys:
    T=T+"%s\t%s\n" % (escape(k), escape(os.environ[k]))
T=T+ "</pre>"
print (S%(sys.version,T))
Du kan teste her https://borres.hiof.no/wep/python/cgi/version.py

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/python3
"""
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)
[3] [4]
Referanser
  1. PuTTY putty.org www.putty.org/ 14-11-2014
  1. Secure Shell Wikipedia en.wikipedia.org/wiki/Secure_Shell 24-03-2014
  1. chmode catcode.com/teachmod/ 14-03-2014
  1. Python - CGI Programming Tutorials Point www.tutorialspoint.com/python/python_cgi_programming.htm 14-03-2010