cgi — wsparcie dla wspólnego sprzęgu bramki - z ang. - Common Gateway Interface

Source code: Lib/cgi.py


Wspierający moduł dla skryptów sprzęgu wspólnej bramki (z ang. CGI).

Ten moduł definiuje pewną liczbę użyteczności dla użycia przez skrypty CGI napisane w języku pytonowskim.

Wprowadzenie

Skrypt CGI jest wywoływany przez serwer HTTP, zwykle aby przetworzyć wejście użytkownika dostarczone przez HTML <FORM> lub element <ISINDEX>.

Najczęściej, Skrypty CGI przebywają w katalogu specjalnym serwera cgi-bin. Serwer HTTP umieszcza wszelkiego rodzaju informacje o zapytaniu (takie jak nazwa hosta klienta, oczekiwany adres URL, ciąg zapytania, i wiele innych dobrych rzeczy) w środowisku powłoki skryptu, wykonuje skrypt i wysyła wyjście skryptu z powrotem do klienta.

Wejście skryptu jest połączone z klientem także, i czasami dane formularza są wczytywane tą drogą także; innym razem dane formularza są przekazywane przez „ciąg zapytania” jako część adresu URL. Ten moduł jest przeznaczony do zajmowania się różnymi przypadkami i dostarczenia prostszego sprzęgu dla skryptu języka pytonowskiego. To także dostarcza pewną liczbę użyteczności które pomagają w odpluskwianiu skryptów zaś najnowszym dodatkiem jest wsparcie dla załadowywania plików przez formularz (jeśli twoja przeglądarka to wspomaga).

Wyjście skryptu CGI powinno składać się z dwóch sekcji, oddzielonych pustą linią. Pierwszy rozdział zawiera liczbę nagłówków, mówiąc klientowi jakiego rodzaju dane nastąpią potem. Kod języka pytonowskiego aby wytworzyć minimalny rozdział nagłówkowy wygląda następująco:

print("Content-Type: text/html")    # HTML is following
print()                             # blank line, end of headers

Drugi rozdziałem jest zwykle HTML, który pozwala oprogramowaniu klienta wyświetlić ładnie sformatowany tekst z nagłówkiem, obrazkami w-jednej-linii itp. Tu jest kod języka pytonowskiego który drukuje prosty kawałek HTML-a:

print("<TITLE>CGI script output</TITLE>")
print("<H1>This is my first CGI script</H1>")
print("Hello, world!")

Użycie modułu cgi

Zacznij przez napisanie import cgi.

Gdy piszesz nowy skrypt, rozważ dodanie tych linii:

import cgitb
cgitb.enable()

To aktywuje specjalną obsługę błędów, która będzie wyświetlać szczegółowe raporty o błędach w przeglądarce sieciowej jeśli nastąpią jakiekolwiek błędy. Jeśli wolisz raczej nie pokazywać wnętrzności swojego programu dla użytkowników twojego skryptu możesz mieć raporty zapisane do pliku zamiast tego, za pomocą kodu takiego jak ten:

import cgitb
cgitb.enable(display=0, logdir="/path/to/logdir")

Jest bardzo przydatnym użycie tej właściwości w czasie rozwijania skryptu. Raporty produkowane przez cgitb dostarczają informację która może oszczędzić Ci wiele czasu w śledzeniu błędów. Możesz zawsze usunąć cgitb linię później gdy przetestowałeś swój skrypt i jesteś pewien że działa on prawidłowo.

To get at submitted form data, use the FieldStorage class. If the form contains non-ASCII characters, use the encoding keyword parameter set to the value of the encoding defined for the document. It is usually contained in the META tag in the HEAD section of the HTML document or by the Content-Type header). This reads the form contents from the standard input or the environment (depending on the value of various environment variables set according to the CGI standard). Since it may consume standard input, it should be instantiated only once.

The FieldStorage instance can be indexed like a Python dictionary. It allows membership testing with the in operator, and also supports the standard dictionary method keys() and the built-in function len(). Form fields containing empty strings are ignored and do not appear in the dictionary; to keep such values, provide a true value for the optional keep_blank_values keyword parameter when creating the FieldStorage instance.

Dla przykładu, następujący kod (który zakłada, że nagłówek Content-Type i puste linie zostały już wydrukowane) sprawdza czy pola nazwa i addr zostały oba ustawione na nie-pusty ciąg znaków:

form = cgi.FieldStorage()
if "name" not in form or "addr" not in form:
    print("<H1>Error</H1>")
    print("Please fill in the name and addr fields.")
    return
print("<p>name:", form["name"].value)
print("<p>addr:", form["addr"].value)
...further form processing here...

Here the fields, accessed through form[key], are themselves instances of FieldStorage (or MiniFieldStorage, depending on the form encoding). The value attribute of the instance yields the string value of the field. The getvalue() method returns this string value directly; it also accepts an optional second argument as a default to return if the requested key is not present.

If the submitted form data contains more than one field with the same name, the object retrieved by form[key] is not a FieldStorage or MiniFieldStorage instance but a list of such instances. Similarly, in this situation, form.getvalue(key) would return a list of strings. If you expect this possibility (when your HTML form contains multiple fields with the same name), use the getlist() method, which always returns a list of values (so that you do not need to special-case the single item case). For example, this code concatenates any number of username fields, separated by commas:

value = form.getlist("username")
usernames = ",".join(value)

If a field represents an uploaded file, accessing the value via the value attribute or the getvalue() method reads the entire file in memory as bytes. This may not be what you want. You can test for an uploaded file by testing either the filename attribute or the file attribute. You can then read the data from the file attribute before it is automatically closed as part of the garbage collection of the FieldStorage instance (the read() and readline() methods will return bytes):

fileitem = form["userfile"]
if fileitem.file:
    # It's an uploaded file; count lines
    linecount = 0
    while True:
        line = fileitem.file.readline()
        if not line: break
        linecount = linecount + 1

FieldStorage objects also support being used in a with statement, which will automatically close them when done.

If an error is encountered when obtaining the contents of an uploaded file (for example, when the user interrupts the form submission by clicking on a Back or Cancel button) the done attribute of the object for the field will be set to the value -1.

Standard ładowania pliku przewiduje możliwość załadowania wielu plików z jednego pola (używając rekursywnego kodowania multipart/*). Gdy to się zdarzy, element będzie słowniko-podobnym elementem FieldStorage. To może być określone przez sprawdzenie właściwości type, która powinna być multipart/form-data (lub byćmoże inny typ MIME pasujący do multipart/*). W tym przypadku może być on iterowany rekursywnie tak, jak przedmiot formularza nadrzędnego poziomu.

Gdy formularz jest podawany w „starym” formacie (jako ciąg zapytania lub pojedyncza część danych typu application/x-www-form-urlencoded), wszystkie elementy właściwie będą przykładami uogólnienia MiniFieldStorage. W tym przypadku, właściwości list, file, i filename są zawsze None.

Formularz podany przez sposób postępowania POST który także zawiera ciąg zapytania będzie zawierał zarówno elementy FieldStorage jak i MiniFieldStorage.

Zmienione w wersji 3.4: The file attribute is automatically closed upon the garbage collection of the creating FieldStorage instance.

Zmienione w wersji 3.5: Added support for the context management protocol to the FieldStorage class.

Sprzęg Wyższego Poziomu (Higher Level Interface)

Poprzednia sekcja wyjaśnia jak czytać dane z formularza CGI używając uogólnienia FieldStorage. Ten rozdział opisuje sprzęg wyższego rzędu, który został dodany do tego uogólnienia aby pozwolić robić to w bardziej czytelny i intuicyjny sposób. Sprzęg nie powoduje że techniki opisane w poprzednich rozdziałach stają się zbędne — one są wciąż użyteczne aby wykonywać ładowanie plików efektywnie, dla przykładu.

Sprzęg składa się z dwóch prostych sposobów postępowania. Używając sposobów postępowania możesz przetwarzać dane w zastępczy sposób bez konieczności martwienia się czy tylko jedna czy więcej danych zostało opublikowanych pod daną nazwą.

W poprzednim rozdziale, dowiedziałeś się jak pisać następujący kod za każdym razem gdy spodziewałeś się że użytkownik opublikuje więcej niż jedną wartość pod jedną nazwą:

item = form.getvalue("item")
if isinstance(item, list):
    # The user is requesting more than one item.
else:
    # The user is requesting only one item.

Ta sytuacja jest obecna dla przykładu gdy formularz zawiera grupę wielu pól zaznaczania o tej samej nazwie:

<input type="checkbox" name="item" value="1" />
<input type="checkbox" name="item" value="2" />

W większości przypadków, jednakże, istnieje tylko jedna urządzenie formularza o określonej nazwie w formularzu i wtedy oczekujesz i potrzebujesz tylko jednej wartości powiązanej z tą nazwą. Więc piszesz skrypt zawierający dla przykładu ten kod:

user = form.getvalue("user").upper()

Problem z kodem polega na tym, że nigdy nie powinieneś oczekiwać, że klient dostarczy właściwe wpisy do twoich skryptów. Dla przykładu, jeśli ciekawy użytkownik dołoży jeszcze jedną parę user=foo do ciągu zapytania, wtedy skrypt ulegnie wypadkowi, ponieważ w tej sytuacji wywołanie sposobu postępowania getvalue("user") zwróci listę zamiast ciągu znaków. Wywołanie sposobu postępowania upper() na liście nie jest prawidłowe (gdyż listy nie mają sposobów postępowania o tej nazwie) i kończy się wyjątkiem AttributeError.

Zatem, właściwym sposobem aby wczytywać dane formularza było zawsze używanie kodu który sprawdza czy otrzymana wartość jest pojedynczą wartością czy listą wartości. To jest irytujące i prowadzi do mniej czytelnych skryptów.

A more convenient approach is to use the methods getfirst() and getlist() provided by this higher level interface.

FieldStorage.getfirst(name, default=None)

Ten sposób postępowania zawsze zwraca tylko jedną wartość związaną z polem formularza name. Sposób postępowania zwraca tylko pierwszą wartość w przypadku gdy więcej wartości zostało opublikowanych pod taką nazwą. Proszę zauważ że kolejność w jakiej wartości są otrzymywane może różnić się między przeglądarkami i nie powinien liczyć się. 1 Jeśli żadne takie pole formularza ani wartość nie istnieje wtedy sposób postępowania zwraca wartość określoną przez opcjonalny parametr domyślny - z ang - default. Ten parametr domyślnie równy jest None jeśli nie jest określony.

FieldStorage.getlist(name)

Ten sposób postępowania zawsze zwraca listę wartości związaną z polem formularza name. Ten sposób postępowania zwraca pustą listę jeśli żadne pole ani wartość nie istnieje dla nazwy - z ang. - name. Zwraca listę składającą się z jednego elementu jeśli tylko jedna taka wartość istnieje.

Używając tych sposobów postępowania możesz napisać ładny zgrabny kod:

import cgi
form = cgi.FieldStorage()
user = form.getfirst("user", "").upper()    # This way it's safe.
for item in form.getlist("item"):
    do_something(item)

Zadania

Te są użyteczne jeśli chcesz więcej kontrolować, lub jeśli chcesz zatrudnić niektóre z algorytmów wypełnionych w tym module w innych przypadkach.

cgi.parse(fp=None, environ=os.environ, keep_blank_values=False, strict_parsing=False, separator="&")

Parse a query in the environment or from a file (the file defaults to sys.stdin). The keep_blank_values, strict_parsing and separator parameters are passed to urllib.parse.parse_qs() unchanged.

Zmienione w wersji 3.8.8: Added the separator parameter.

cgi.parse_multipart(fp, pdict, encoding="utf-8", errors="replace", separator="&")

Parse input of type multipart/form-data (for file uploads). Arguments are fp for the input file, pdict for a dictionary containing other parameters in the Content-Type header, and encoding, the request encoding.

Returns a dictionary just like urllib.parse.parse_qs(): keys are the field names, each value is a list of values for that field. For non-file fields, the value is a list of strings.

This is easy to use but not much good if you are expecting megabytes to be uploaded — in that case, use the FieldStorage class instead which is much more flexible.

Zmienione w wersji 3.7: Added the encoding and errors parameters. For non-file fields, the value is now a list of strings, not bytes.

Zmienione w wersji 3.8.8: Added the separator parameter.

cgi.parse_header(string)

Wczytaj nagłówek MIME (taki, jak Content-Type) do głównej wartości i słownika parametrów.

cgi.test()

Wydajny testowy skrypt CGI, użyteczny jako program główny. Zapisuje minimalne nagłówki HTTP i formatuje całą informację dostarczoną dla skryptu w formularzu HTML.

cgi.print_environ()

Formatuje środowisko powłoki w HTML-u

cgi.print_form(form)

Formatuje formularz w HTML-u.

cgi.print_directory()

Formatuje obecny katalog w HTML-u.

cgi.print_environ_usage()

Drukuje listę użytecznych (używanych przez CGI) zmiennych środowiskowych w HTML-u.

Troska o bezpieczeństwo

Istnieje jedna istotna zasada: Jeśli wywołujesz zewnętrzny program (przez zadanie os.system() lub os.popen() lub inne o podobnym działaniu), upewnij się bardzo że nie przekazujesz dowolnych ciągów znaków otrzymanych od klienta do powłoki. To jest bardzo znana luka bezpieczeństwa gdzie sprytni hakerzy dowolnie w sieci mogą wykorzystać naiwny skrypt CGI aby wywołać dowolną komendę powłoki. Nawet częściom adresu URL lub nazwom pól nie można ufać, gdyż zapytanie może nie pochodzić z formularza!

Aby trzymać się bezpiecznej strony, jeśli przekazujesz ciąg znaków otrzymany z formularza do polecenia powłoki, powinieneś upewnić się, że ciąg znaków zawiera tylko znaki alfanumeryczne, ukośniki, podkreślenia i kropki.

Instalowanie twojego skryptu CGI na systemie Unix-owym

Przeczytaj dokumentację dla twojego serwera HTTP i sprawdź u swojego administratora systemu aby dowiedzieć się który katalog powinien być użyty dla instalowania skryptów CGI; zwykle to jest katalog cgi-bin w drzewie serwera.

Upewnij się że twój skrypt jest czytelny i wykonywalny przez „innych”; tryb pliku Unix-a powinien być 0o755 ósemkowo (użyj chmod 0755 nazwa-pliku). Upewnij się, że pierwsza linia skryptu zawiera #! zaczynając w kolumnie 1 po której następuje ścieżka dostępu do pliku programu interpretującego polecenia języka pytonowskiego, dla przykładu:

#!/usr/local/bin/python

Upewnij się że program interpretujący polecenia języka pytonowskiego istnieje i jest wykonywalny przez „innych”.

Upewnij się, że jakiekolwiek pliki które twój skrypt chce odczytać lub zapisać są czytelne i możliwe do zapisania odpowiednio, przez „innych” — ich tryb powinien być 0o644 dla czytelnych i 0o666`` dla możliwych do zapisania. To jest ponieważ z powodów bezpieczeństwa, serwer HTTP wykonuje twój skrypt jako użytkownik „nikt” - z ang. - „nobody” bez żadnych szczególnych przywilejów. Może tylko wczytywać (zapisywać, wykonywać) pliki które wszyscy mogą czytać (zapisać, wykonać). Bierzący katalog w czasie wykonania jest także inny (jest to zwykle katalog serwera cgi-bin) i ustawienie zmiennych środowiskowych jest także inne od tego które dostajesz gdy się zalogujesz. W szczególności, nie licz na ścieżkę przeszukiwania pod kątem plików wykonywalnych (PATH) ani na ścieżkę przeszukiwania modułów języka pytonowskiego (PYTHONPATH), że będą ustawione na cokolwiek interesującego.

Jeśli potrzebujesz załadować moduły z katalogu który nie jest domyślną ścieżką przeszukiwania modułów języka pytonowskiego, możesz zmienić ścieżkę w twoim skrypcie, przez importowaniem innych modułów. Dla przykładu:

import sys
sys.path.insert(0, "/usr/home/joe/lib/python")
sys.path.insert(0, "/usr/local/lib/python")

(W ten sposób, katalog wstawiony jako ostatni będzie przeszukiwany jako pierwszy!)

Instrukcje dla nie-Unixowych systemów będą różne; Sprawdź dokumentację serwera HTTP (będzie zwykle miała sekcję o skryptach CGI).

Sprawdzanie twoich skryptów CGI

Niestety, skrypt CGI zwykle nie uruchomi się gdy spróbujesz go uruchomić z wiersza poleceń i skrypt który działa dobrze z wiersza polecenia może zawieźć nieoczekiwanie gdy uruchomiony z serwera. Jest jeden powód dla którego wciąż powinieneś testować swój skrypt z wiersza polecenia: jeśli zawiera błąd składniowy, program interpretujący polecenia języka pytonowskiego nie wykona go w ogóle, a serwer HTTP najprawdopodobniej wyśle tajemniczy komunikat o błędzie do klienta.

Zakładając, że twój skrypt nie ma błędów składniowych, a jednak wciąż nie działa, nie masz wyboru, tylko musisz czytać dalej.

Odpluskwianie skryptów CGI

First of all, check for trivial installation errors — reading the section above on installing your CGI script carefully can save you a lot of time. If you wonder whether you have understood the installation procedure correctly, try installing a copy of this module file (cgi.py) as a CGI script. When invoked as a script, the file will dump its environment and the contents of the form in HTML form. Give it the right mode etc, and send it a request. If it’s installed in the standard cgi-bin directory, it should be possible to send it a request by entering a URL into your browser of the form:

http://yourhostname/cgi-bin/cgi.py?name=Joe+Blow&addr=At+Home

Jeśli zwróci błąd typu 404, wtedy serwer nie może znaleźć skryptu – być może potrzebujesz zainstalować go w innych katalogu. Jeśli daje inny błąd, istnieje problem instalacji który powinieneś naprawić zanim spróbujesz iść dalej. Jeśli otrzymujesz ładnie sformatowany wypis środowiska i zawartości formularza (w tym przypadku, pola powinny być wypisane jako „addr” z wartością „At Home” i „name” z wartością „Joe Blow”), plik skryptu cgi.py zostały zainstalowane poprawnie. Jeśli wykonasz tę samą procedurę dla twojego własnego skryptu, powinieneś móc go teraz odpluskwiać.

Następnym krokiem mogłoby być wezwanie zadania test() modułu cgi z twojego skryptu: zamiana jej głównego kodu na pojedyncze stwierdzenie:

cgi.test()

To powinno dać te same wyniki jak te otrzymane z zainstalowania samego pliku cgi.py.

Gdy zwykły skrypt języka pytonowskiego zgłasza nieobsłużony wyjątek (dla jakiegokolwiek powodu: z powodu literówki w nazwie modułu, pliku który nie może być otwarty, itp.), program interpretujący polecenia języka pytonowskiego wypisuje ładny wypis i wychodzi. Podczas gdy program interpretujący polecenia języka pytonowskiego będzie wciąż to robił gdy twój skrypt CGI zgłosi wyjątek, najprawdopodobniej wypis skończy w jednym z plików logów serwera HTTP, lub zostanie odrzucony całkowicie.

Szczęśliwie, gdy już uda ci się wykonać jakiś kod za pomocą skryptu, możesz łatwo wysyłać wypisy do przeglądarki używając cgitb. Jeśli jeszcze nie zrobiłeś tego, po prostu dodaj linie:

import cgitb
cgitb.enable()

na górę twojego skryptu. Wtedy spróbuj uruchomić go jeszcze raz; gdy problem pojawi się znów, powinieneś zobaczyć szczegółowy raport, który prawdopodobnie uczyni jasnym powód wypadku.

Jeśli podejrzewasz, że może być problem w importowaniu modułu cgitb, możesz użyć nawet jeszcze bardziej wydajnego podejścia (które używa tylko modułów wbudowanych):

import sys
sys.stderr = sys.stdout
print("Content-Type: text/plain")
print()
...your code here...

To opiera się na programie interpretującym polecenia języka pytonowskiego, że wydrukuje swój wypis. Typ treści wyjścia jest ustawiony na zwykły tekst, co wyklucza całą obsługę HTML. Jeśli twój skrypt działa, surowy HTML zostanie pokazany przez twojego klienta. Jeśli zgłasza wyjątek, najprawdopodobniej po tym jak pierwsze linie zostaną wypisane, wypis wsteczny zostanie wyświetlony. Ponieważ żadna interpretacja HTML-a nie miejsca, wypis wsteczny będzie czytelny.

Typowe problemy i ich rozwiązania

  • Większość serwerów HTTP przechowuje w przestrzeni wymiany wyjście ze skryptów CGI dopóki skrypt się nie zakończy. To oznacza, że nie jest możliwe wyświetlenie raportu postępu na wyświetlaczu klienta kiedy skrypt działa.

  • Sprawdź instrukcje instalacyjne powyżej.

  • Sprawdź pliki logu serwera HTTP. (tail -f logfile w oddzielnym oknie może być użyteczne!)

  • Zawsze sprawdzaj skrypt dla błędów składniowych najpierw, przez wykonanie czegoś podobnego do python script.py.

  • Jeśli twój skrypt nie ma żadnych błędów składniowych, spróbuj dodać import cgitb; cgitb.enable() na górze skryptu.

  • Gdy wywoływane są zewnętrzne programy upewnij się że mogą być znalezione. Zwykle to oznacza, że używanie nazw ścieżek absolutnych — PATH nie jest zwykle ustawiana na użyteczną wartość skryptu CGI.

  • Gdy wczytujesz lub zapisujesz zewnętrzne pliki, upewnij się że mogą one być wczytane lub zapisane przez userid pod którym twój skrypt CGI będzie działał: to jest typowo userid pod którym serwer sieci działa, lub pewny jawnie określony userid dla właściwości sieciowego serwera suexec.

  • Nie próbuj nadawać skryptowi CGI trybu set-uid. To nie działa na większości systemów, i jest odpowiedzialnością za bezpieczeństwo także.

Przypisy

1

Zauważ że pewne niedawne wersje specyfikacji HTML-a określają w jakiej kolejności wartości pól powinny być dostarczone, ale wiedzieć czy zapytanie zostało otrzymane z odpowiadającej przeglądarki, czy też w ogóle z przeglądarki jest nużące i podatne na błędy.