Два уровня погружения, выбирай по интересу:
Уровень 1 - http.server из stdlib:
Самый быстрый старт. Наследуешься от BaseHTTPRequestHandler, переопределяешь do_GET:
from http.server import HTTPServer, BaseHTTPRequestHandler
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header('Content-Type', 'text/html; charset=utf-8')
self.end_headers()
self.wfile.write(b'<h1>Hello</h1>')
def log_message(self, format, *args):
pass # глушим логи
if __name__ == '__main__':
server = HTTPServer(('0.0.0.0', 8080), Handler)
print('Server running on :8080')
server.serve_forever()
Запускаешь, открываешь http://localhost:8080 - работает.
Уровень 2 - сырые сокеты:
Понимаешь HTTP как текстовый протокол. Принимаешь TCP-соединение, читаешь строки запроса, пишешь ответ вручную:
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind(('0.0.0.0', 8080))
server.listen(5)
while True:
conn, addr = server.accept()
data = conn.recv(4096).decode('utf-8')
# data содержит сырой HTTP-запрос
response = 'HTTP/1.1 200 OKrnContent-Type: text/htmlrnrn<h1>Hello</h1>'
conn.sendall(response.encode('utf-8'))
conn.close()
Для понимания протокола рекомендую второй путь. Когда поймешь что такое rnrn между заголовками и телом, магия фреймворков начнет рассеиваться.
Отличный пример с сокетами, именно это и искал. А как правильно парсить путь из запроса чтобы разные URL возвращали разный контент?
Первая строка сырого запроса выглядит как `GET /path HTTP/1.1`. Делаешь `request_line = data.split('rn')[0]`, потом `method, path, _ = request_line.split(' ')` - и `path` у тебя в кармане. Дальше обычный `if path == '/about':`.