# 21.2. `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.

## 21.2.1. 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!")

## 21.2.2. 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.

## 21.2.3. 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)

## 21.2.4. 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.

`cgi.``parse_qs`(qs, keep_blank_values=False, strict_parsing=False)

  This function is deprecated in this module. Use `urllib.parse.parse_qs()` instead. It is maintained here only for backward compatibility.

`cgi.``parse_qsl`(qs, keep_blank_values=False, strict_parsing=False)

  This function is deprecated in this module. Use `urllib.parse.parse_qsl()` instead. It is maintained here only for backward compatibility.

`cgi.``parse_multipart`(fp, pdict)

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

  Returns a dictionary just like `urllib.parse.parse_qs()` keys are the field names, each value is a list of values for that field. 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.

  Note that this does not parse nested multipart parts — use `FieldStorage` for that.

  Zmienione w wersji 3.6.13: 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.

`cgi.``escape`(s, quote=False)

  Convert the characters `'&'`, `'<'` and `'>'` in string s to HTML-safe sequences. Use this if you need to display text that might contain such characters in HTML. If the optional flag quote is true, the quotation mark character (`"`) is also translated; this helps for inclusion in an HTML attribute value delimited by double quotes, as in `<a href="...">`. Note that single quotes are never translated.

  Niezalecane od wersji 3.2: This function is unsafe because quote is false by default, and therefore deprecated. Use `html.escape()` instead.

## 21.2.5. 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.

## 21.2.6. 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).

## 21.2.7. 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.

## 21.2.8. 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.

## 21.2.9. 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.