Last modified: June 06, 2026

This article is written in: 🇵🇱

HTTP i prosty serwer

HTTP (Hypertext Transfer Protocol) to protokół warstwy aplikacji w modelu OSI, używany głównie do przesyłania danych między klientem (zwykle przeglądarką internetową) a serwerem. Protokół ten opiera się na modelu żądanie-odpowiedź, gdzie klient wysyła zapytanie HTTP, a serwer zwraca odpowiedź. HTTP jest protokółem bezstanowym, co oznacza, że każde żądanie jest niezależne i serwer nie utrzymuje informacji o poprzednich żądaniach.

Typy żądań HTTP

HTTP definiuje kilka metod, które określają, jak klient komunikuje się z serwerem:

Metoda Opis
GET Służy do pobierania danych. Powinno być idempotentne, co oznacza, że nie zmienia stanu.
POST Używane do wysyłania danych na serwer, np. tworzenia nowych zasobów. Nie jest idempotentne.
PUT Służy do aktualizacji istniejących zasobów lub ich tworzenia, jeśli nie istnieją.
DELETE Usuwa wskazany zasób z serwera.
HEAD Podobne do GET, ale zwraca tylko nagłówki odpowiedzi bez treści.
OPTIONS Pyta serwer o dostępne metody dla danego zasobu.

Statusy odpowiedzi HTTP

Każda odpowiedź HTTP zawiera kod statusu, który wskazuje na wynik żądania:

Kod Opis
200 OK Żądanie zakończyło się sukcesem.
201 Created Zasób został pomyślnie stworzony (np. przez POST).
204 No Content Żądanie zakończyło się sukcesem, ale odpowiedź nie zawiera treści.
400 Bad Request Żądanie było niepoprawne lub niemożliwe do zrealizowania.
401 Unauthorized Klient musi się uwierzytelnić.
403 Forbidden Klient nie ma uprawnień do dostępu do zasobu.
404 Not Found Zasób nie został znaleziony.
500 Internal Server Error Wewnętrzny błąd serwera.

Wysyłanie żądań HTTP w Pythonie

W Pythonie jednym z najczęściej używanych modułów do obsługi żądań HTTP jest requests. Jest on prosty w użyciu, a jednocześnie potężny, umożliwiając wykonywanie skomplikowanych operacji HTTP z łatwością.

Poniżej rozszerzony przykład, który pokazuje, jak wykonać żądanie GET oraz jak zarządzać nagłówkami i parametrami:

import requests

url = "https://jsonplaceholder.typicode.com/posts"
headers = {
    'User-Agent': 'my-app/0.0.1',
    'Accept': 'application/json'
}
params = {
    'userId': 1
}

response = requests.get(url, headers=headers, params=params)

print(f'Status code: {response.status_code}')  # Sprawdzenie statusu odpowiedzi
print(f'Response headers: {response.headers}')  # Nagłówki odpowiedzi
print(f'Response JSON: {response.json()}')      # Zawartość odpowiedzi w formacie JSON

W powyższym przykładzie dodano nagłówki oraz parametry zapytania, co pokazuje elastyczność modułu requests. Dzięki temu można modyfikować żądania w zależności od potrzeb aplikacji.

Serwer HTTP

Python dostarcza moduł http.server, który umożliwia uruchomienie prostego serwera HTTP. Jest to szczególnie przydatne w fazie rozwoju, kiedy chcemy przetestować naszą stronę lub aplikację lokalnie. Serwer ten jest jednak prosty i nie powinien być używany w środowisku produkcyjnym, ponieważ nie zapewnia zabezpieczeń ani wydajności odpowiedniej dla rzeczywistych aplikacji.

Prosty serwer HTTP

Poniższy przykład pokazuje, jak uruchomić prosty serwer HTTP, który serwuje pliki z bieżącego katalogu:

import http.server
import socketserver

PORT = 8000
Handler = http.server.SimpleHTTPRequestHandler

with socketserver.TCPServer(("", PORT), Handler) as httpd:
    print(f"Serving files from the current directory at port {PORT}")
    httpd.serve_forever()

Dostosowywanie serwera

Aby dostosować serwer do specyficznych potrzeb, można rozszerzyć klasę SimpleHTTPRequestHandler, np. aby obsługiwać dodatkowe metody HTTP, takie jak POST. Poniżej przedstawiam przykład niestandardowej obsługi żądań:

class CustomHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header("Content-type", "text/html")
        self.end_headers()
        self.wfile.write(b"Custom GET response")

    def do_POST(self):
        content_length = int(self.headers['Content-Length'])
        post_data = self.rfile.read(content_length)
        print(f"Received POST data: {post_data.decode('utf-8')}")

        self.send_response(200)
        self.send_header("Content-type", "text/html")
        self.end_headers()
        self.wfile.write(b"POST received")

PORT = 8000
with socketserver.TCPServer(("", PORT), CustomHTTPRequestHandler) as httpd:
    print(f"Serving custom responses at port {PORT}")
    httpd.serve_forever()

Takie podejście daje pełną kontrolę nad tym, jak serwer ma odpowiadać na różne typy żądań, co może być szczególnie przydatne podczas tworzenia API lub prostych aplikacji webowych bez konieczności korzystania z zewnętrznych frameworków.

Wady prostego serwera

Mimo że serwer dostarczany przez moduł http.server jest wygodny, jego ograniczenia sprawiają, że nie nadaje się do użytku produkcyjnego:

Zaawansowane użycie modułu requests

POST, PUT, DELETE i obsługa danych JSON

import requests

base_url = "https://jsonplaceholder.typicode.com"

# POST — tworzenie nowego zasobu
nowy_post = {
    "title": "Nowy post",
    "body": "Treść posta",
    "userId": 1
}
odpowiedz = requests.post(f"{base_url}/posts", json=nowy_post)
print(odpowiedz.status_code)   # 201 Created
print(odpowiedz.json())

# PUT — aktualizacja zasobu
aktualizacja = {"title": "Zmieniony tytuł", "body": "Nowa treść", "userId": 1}
odpowiedz = requests.put(f"{base_url}/posts/1", json=aktualizacja)
print(odpowiedz.status_code)   # 200 OK

# DELETE — usunięcie zasobu
odpowiedz = requests.delete(f"{base_url}/posts/1")
print(odpowiedz.status_code)   # 200 OK

Obsługa błędów i timeouty

import requests
from requests.exceptions import Timeout, ConnectionError, HTTPError

try:
    odpowiedz = requests.get("https://example.com/api/data", timeout=5)
    odpowiedz.raise_for_status()   # Rzuca HTTPError dla 4xx i 5xx
    print(odpowiedz.json())
except Timeout:
    print("Żądanie przekroczyło limit czasu")
except ConnectionError:
    print("Błąd połączenia — brak internetu lub błędny host")
except HTTPError as e:
    print(f"Błąd HTTP: {e.response.status_code}")

Sesje — wielokrotne żądania z tymi samymi parametrami

Sesja (Session) zachowuje cookies i nagłówki między żądaniami:

import requests

with requests.Session() as sesja:
    # Ustawienie wspólnych nagłówków dla wszystkich żądań w sesji
    sesja.headers.update({
        "Authorization": "Bearer moj-token",
        "Accept": "application/json"
    })

    # Pierwsze żądanie — logowanie
    odpowiedz = sesja.post("https://api.example.com/login",
                           json={"user": "jan", "password": "tajne"})

    # Kolejne żądania używają tych samych cookies i nagłówków
    profil = sesja.get("https://api.example.com/profile")
    print(profil.json())

Moduł urllib — standardowa biblioteka

Dla prostych przypadków nie potrzebujemy requests — wystarczy wbudowany urllib.request:

import urllib.request
import urllib.parse
import json

# GET
with urllib.request.urlopen("https://jsonplaceholder.typicode.com/posts/1") as resp:
    dane = json.loads(resp.read().decode())
    print(dane["title"])

# POST
dane_post = json.dumps({"title": "test", "body": "treść", "userId": 1}).encode()
req = urllib.request.Request(
    "https://jsonplaceholder.typicode.com/posts",
    data=dane_post,
    headers={"Content-Type": "application/json"},
    method="POST"
)
with urllib.request.urlopen(req) as resp:
    print(resp.status)   # 201

Nagłówki i uwierzytelnianie

import requests
from requests.auth import HTTPBasicAuth, HTTPDigestAuth

# Basic Auth
odpowiedz = requests.get("https://httpbin.org/basic-auth/user/pass",
                         auth=HTTPBasicAuth("user", "pass"))
print(odpowiedz.status_code)   # 200

# Token Bearer (JWT)
headers = {"Authorization": "Bearer eyJhbGci..."}
odpowiedz = requests.get("https://api.example.com/secure", headers=headers)

# API Key w parametrach zapytania
odpowiedz = requests.get("https://api.example.com/data",
                         params={"api_key": "moj-klucz-api"})

Kody statusu HTTP — najważniejsze grupy

Zakres Kategoria Najczęstsze kody
1xx Informacyjne 100 Continue, 101 Switching Protocols
2xx Sukces 200 OK, 201 Created, 204 No Content
3xx Przekierowanie 301 Moved Permanently, 302 Found, 304 Not Modified
4xx Błąd klienta 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found
5xx Błąd serwera 500 Internal Server Error, 502 Bad Gateway, 503 Service Unavailable

Obsługa błędów i retry

import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

# Konfiguracja automatycznego ponowienia prób
session = requests.Session()
retry = Retry(
    total=3,              # maksymalnie 3 próby
    backoff_factor=0.5,   # opóźnienie: 0.5s, 1s, 2s
    status_forcelist=[500, 502, 503, 504]  # ponawiaj dla tych kodów
)
adapter = HTTPAdapter(max_retries=retry)
session.mount("http://", adapter)
session.mount("https://", adapter)

# Użycie sesji z retry
try:
    odpowiedz = session.get("https://api.example.com/data", timeout=10)
    odpowiedz.raise_for_status()   # Rzuci wyjątek dla kodów 4xx/5xx
    dane = odpowiedz.json()
except requests.exceptions.Timeout:
    print("Przekroczono limit czasu!")
except requests.exceptions.HTTPError as e:
    print(f"Błąd HTTP: {e.response.status_code}")
except requests.exceptions.ConnectionError:
    print("Brak połączenia z serwerem!")

Porównanie: requests vs httpx vs urllib vs aiohttp

Biblioteka Synchroniczna Asynchroniczna HTTP/2 Wbudowana? Kiedy używać
urllib Tak Nie Nie Tak Proste skrypty bez zależności
requests Tak Nie Nie Nie Większość aplikacji (standard)
httpx Tak Tak Tak Nie Nowoczesne API, async+sync
aiohttp Nie Tak Nie Nie Wysokowydajne async aplikacje