Сервер python: Простой P2P сервер на python / Хабр
Простой P2P сервер на python / Хабр
Одноранговая сеть или проще P2P сеть — это сеть в которой все пользователи равны и имеют равные права. Отличительная особенность таких сетей от обычных в том, что в ней нет единого сервера, к которому подключаются пользователи, вместо этого они подключаются друг к другу. Существуют гибридные варианты таких сетей, в котором присутствует сервер, выполняющий только координирующую работу.
Сегодня я хочу предложить простой вариант реализации P2P сервера для такой сети на языке python.
На 1-ом курсе обучения в вузе мне преподаватель по программированию предложил написать мне децентрализованный чат. Язык и модули я выбирал сам. Такое предложение меня сразу заинтересовало, тем более я давно хотел начать изучать python, да и мог не посещать её пары. Недолго думая я согласился и принялся к работе.
Самым трудным для меня оказалось написать серверную часть. Если интерфейс я написал практически сразу, на него я потратил порядка 2 дней, то над серверной частью мне пришлось подзадуматься. Думал я неделю, но зато на следующий день за пару часов написал рабочий P2P сервер для своего чата.
Исходный код всего сервера расположен ниже.
Импорты
import socket
import rsa
from threading import Thread
from time import sleep
import datetime
Сервер
# P2P сервер
class P2P:
def __init__(self, _port: int, _max_clients: int = 1):
# Индикатор работы сервера
self.running = True
# Порт сервера
self.port = _port
# Максимальное кол-во подключений
self.max_clients = _max_clients
# Подключённые пользователи
self.clients_ip = ["" for i in range(self.max_clients)]
# Словарь с входящими сообщениями
self.incoming_requests = {}
# Логи клиентов
self.clients_logs = [Log for i in range(self.max_clients)]
# Клиентские сокеты
self.client_sockets = [socket.socket() for i in range(self.max_clients)]
# Таймауты клиентов
for i in self.client_sockets:
i.settimeout(0.2)
# Ключи для шифрования исходящих сообщений
self.keys = [rsa.key.PublicKey for i in range(self.max_clients)]
# Ключи для дешифрования входящих сообщений
self.my_keys = [rsa.key.PrivateKey for i in range(self.max_clients)]
# Информация загруженности сокетов
self.socket_busy = [False for i in range(self.max_clients)]
# Чёрный список
self.blacklist = ["127.0.0.1"] + Log.read_and_return_list("blacklist.txt")
# Серверный сокет
self.server_socket = socket.socket()
# Таймаут сервера
self.server_socket.settimeout(0.2)
# Бинд сервера
self.server_socket.bind(('localhost', _port))
self.server_socket.listen(self.max_clients)
self.log = Log("server.log")
self.log.save_data("Server initialized")
# server control
# Создаёт сессию с этим пользователем
def create_session(self, _address: str):
self.log.save_data("Creating session with {}".format(_address))
ind = self.__get_free_socket()
if _address in self.blacklist:
self.log.save_data("{} in blacklist".format(_address))
return
if ind is None:
self.log.save_data("All sockets are busy, can`t connect to {}".format(_address))
return
try:
self.__add_user(_address)
thread = Thread(target=self.__connect, args=(_address, 1))
thread.start()
thread.join(0)
connection, address = self.server_socket.accept()
connection.settimeout(0.2)
except OSError:
self.log.save_data("Failed to create session with {}".format(_address))
self.__del_user(_address)
return
my_key = rsa.newkeys(512)
self.raw_send(_address, my_key[0].save_pkcs1())
key = connection.recv(162).decode()
self.clients_logs[ind].save_data("from {}: {}".format(_address, key))
key = rsa.PublicKey.load_pkcs1(key)
self.__add_keys(_address, key, my_key[1])
while self.running and self.socket_busy[ind]:
try:
data = connection.recv(2048)
except socket.timeout:
continue
except OSError:
self.close_connection(_address)
return
if data:
data = rsa.decrypt(data, self.my_keys[ind])
self.__add_request(_address, data)
try:
self.close_connection(_address)
except TypeError or KeyError:
pass
# Подключается к пользователю
def __connect(self, _address: str, *args):
ind = self.__get_ind_by_address(_address)
try:
self.client_sockets[ind].connect((_address, self.port))
self.socket_busy[ind] = True
return True
except OSError:
return False
# Перезагружает сокет
def __reload_socket(self, _ind: int):
self.client_sockets[_ind].close()
self.client_sockets[_ind] = socket.socket()
self.socket_busy[_ind] = False
# Закрывает соединение
def close_connection(self, _address: str):
ind = self.__get_ind_by_address(_address)
self.__del_key(_address)
self.__reload_socket(ind)
self.__del_user(_address)
# Останавливает сервер
def kill_server(self):
self.running = False
sleep(1)
self.server_socket.close()
self.log.kill_log()
for i in self.client_sockets:
i.close()
for i in self.clients_logs:
try:
i.kill_log()
except TypeError:
pass
# Отправляет сообщение с шифрованием
def send(self, _address: str, _message: str):
ind = self.__get_ind_by_address(_address)
try:
self.clients_logs[ind].save_data("to {}: {}".format(_address, _message))
self.client_sockets[ind].send(rsa.encrypt(_message.encode(), self.keys[ind]))
self.log.save_data("Send message to {}".format(_address))
except OSError:
self.log.save_data("Can`t send message to {}".format(_address))
# Отправляет сообщение без шифрования
def raw_send(self, _address: str, _message: bytes):
ind = self.__get_ind_by_address(_address)
try:
self.client_sockets[ind].send(_message)
self.clients_logs[ind].save_data("to {}: {}".format(_address, _message))
self.log.save_data("Raw send message to {}".format(_address))
except OSError:
self.log.save_data("Raw send to {} Failed".format(_address))
# add
# Добавляет пользователя
def __add_user(self, _address: str):
ind = self.__get_free_socket()
self.clients_logs[ind] = Log("{}.log".format(_address))
self.clients_ip[ind] = _address
self.incoming_requests[_address] = []
self.log.save_data("Added user {}".format(_address))
# Добавляет ключ для шифрования и дешифрования адресу
def __add_keys(self, _address: str, _key: rsa.key.PublicKey, _my_key: rsa.key.PrivateKey):
ind = self.__get_ind_by_address(_address)
try:
self.keys[ind] = _key
self.my_keys[ind] = _my_key
except TypeError:
return
# Добавляет входящее сообщение от адреса
def __add_request(self, _address: str, _message: bytes):
self.incoming_requests[_address].append(_message.decode())
self.clients_logs[self.__get_ind_by_address(_address)].save_data("from {}: {}".format(_address, str(_message)))
self.log.save_data("Get incoming message from {}".format(_address))
# get
# Возвращает индекс первого свободного соккета
# if self.__get_free_socket() is not None: *
def __get_free_socket(self):
for i in range(len(self.socket_busy)):
if not self.socket_busy[i]:
return i
return None
# Возвращает номер индекса, к которому подключён адрес
def __get_ind_by_address(self, _address: str):
for i in range(len(self.clients_ip)):
if self.clients_ip[i] == _address:
return i
else:
return None
# Возвращает входящее сообщение от адреса
def get_request(self, _address: str):
data = self.incoming_requests[_address][0]
self.incoming_requests[_address] = [self.incoming_requests[_address][i]
for i in range(1, len(self.incoming_requests[_address]))]
return data
# check
# Проверяет наличие входящих сообщения от пользователя
# if self.check_request(_address): *
def check_request(self, _address: str):
return bool(self.incoming_requests.get(_address))
# return True if you already connected to _address else False
def check_address(self, _address: str):
return True if _address in self.clients_ip else False
# del
# Удаляет пользователя
def __del_user(self, _address: str):
ind = self.__get_ind_by_address(_address)
self.clients_logs[ind].kill_log()
self.clients_logs[ind] = Log
self.clients_ip[ind] = ""
self.incoming_requests.pop(_address)
self.log.save_data("Deleted user {}".format(_address))
# Удаляет пользователя
def __del_key(self, _address: str):
ind = self.__get_ind_by_address(_address)
self.keys[ind] = rsa.key.PublicKey
self.my_keys[ind] = rsa.key.PrivateKey
# others
# Возвращает число подключённых пользователей
def __len__(self):
num = 0
for i in self.clients_ip:
if i != "":
num += 1
return num
# возвращает Правду если есть хотя бы одно подключение
def __bool__(self):
for i in self.clients_ip:
if i != "":
return True
return False
Лог
class Log:
def __init__(self, _name: str):
self.name = _name
try:
self.file = open(_name, "a")
except FileNotFoundError:
self.file = open(_name, "w")
self.save_data("Log started at " + str(datetime.datetime.now()))
self.file.close()
# Сохраняет информацию в файл
def save_data(self, _data: str):
self.file = open(self.name, "a")
self.file.write("{}\n".format(_data))
self.file.close()
# Возвращает данные из файла в виде листа
@staticmethod
def read_and_return_list(_name: str):
try:
file = open(_name, "r")
except FileNotFoundError:
return []
data = file.read()
return data.split("\n")
# Останавливает лог
def kill_log(self):
self.file = open(self.name, "a")
self.save_data("Log stopped at {}\n".format(datetime.datetime.now()))
self.file.close()
А теперь приступим к разбору и объяснению. Все серверные функции мы разделим на условные категории в зависимости от того, что они делают:
- инициализация
- add функции
- del функции
- check функции
- get функции
- server control функции
- Другие функции
Инициализация
init
def __init__(self, _port: int, _max_clients: int = 1):
# Индикатор работы сервера
self.running = True
# Порт сервера
self.port = _port
# Максимальное кол-во подключений
self.max_clients = _max_clients
# Подключённые пользователи
self.clients_ip = ["" for i in range(self.max_clients)]
# Словарь с входящими сообщениями
self.incoming_requests = {}
# Логи клиентов
self.clients_logs = [Log for i in range(self.max_clients)]
# Клиентские сокеты
self.client_sockets = [socket.socket() for i in range(self.max_clients)]
# Таймауты клиентов
for i in self.client_sockets:
i.settimeout(0.2)
# Ключи для шифрования исходящих сообщений
self.keys = [rsa.key.PublicKey for i in range(self.max_clients)]
# Ключи для дешифрования входящих сообщений
self.my_keys = [rsa.key.PrivateKey for i in range(self.max_clients)]
# Информация загруженности сокетов
self.socket_busy = [False for i in range(self.max_clients)]
# Чёрный список
self.blacklist = ["127.0.0.1"] + Log.read_and_return_list("blacklist.txt")
# Серверный сокет
self.server_socket = socket.socket()
# Таймаут сервера
self.server_socket.settimeout(0.2)
# Бинд сервера
self.server_socket.bind(('localhost', _port))
self.server_socket.listen(self.max_clients)
self.log = Log("server.log")
self.log.save_data("Server initialized")
Для инициализации сервера запросим порт, на котором будем запускать сервер и максимальное кол-во подключений, по умолчанию 1. Сам сервер будет хранить такие данные:
- Индикатор работы
- Порт
- Максимальное кол-во соединений
Листы длиной равной максимальному количеству пользователей:
- Ip подключённых клиентов
- Клиентские сокеты
- Ключи для шифрования
- Ключи для дешифрования
- Индикатор загруженности сокетов
Также объявим чёрный список адресов, который будет загружаться из файла и постоянно содержать адрес «127.0.0.1» во избежание «двойного подключения» к себе самому ( localhost всё ещё доступен), и словарь, который будет хранить входящие сообщения. И нужно установить серверному сокету максимальное кол-во подключений командой listen().
add функции
Все функции в этой категории будут работать только внутри класса. Неправильное обращение с ними может вызвать неправильную работу сервера.
Функция add_user добавляет указанный адрес в рабочие листы сервера, а также запускает лог диалога с пользователем.
add_user
def __add_user(self, _address: str):
ind = self.__get_free_socket()
self.clients_logs[ind] = Log("{}.log".format(_address))
self.clients_ip[ind] = _address
self.incoming_requests[_address] = []
self.log.save_data("Added user {}".format(_address))
Функция add_keys добавляет ключи для шифрования и дешифрования указанному адресу.
add_keys
def __add_keys(self, _address: str, _key: rsa.key.PublicKey, _my_key: rsa.key.PrivateKey):
ind = self.__get_ind_by_address(_address)
try:
self.keys[ind] = _key
self.my_keys[ind] = _my_key
except TypeError:
return
И последняя функция add_request добавляет в словарь входящих сообщений сообщение от указанного адреса.
add_request
def __add_request(self, _address: str, _message: bytes):
self.incoming_requests[_address].append(_message.decode())
self.clients_logs[self.__get_ind_by_address(_address)].save_data("from {}: {}".format(_address, str(_message)))
self.log.save_data("Get incoming message from {}".format(_address))
del функции
Эти функции также как и прошлые работают с внутренними данными сервера. Но они, в отличии от прошлых, удаляют данные а не добавляют.
Функция del_user противоположна функции add_user. Она удаляет всё, что связано с указанным адресом с сервером, а также закрывает лог.
del_user
def __del_user(self, _address: str):
ind = self.__get_ind_by_address(_address)
self.clients_logs[ind].kill_log()
self.clients_logs[ind] = Log
self.clients_ip[ind] = ""
self.incoming_requests.pop(_address)
self.log.save_data("Deleted user {}".format(_address))
Функция del_key удаляет ключи для шифрования и дешифрования указанного адреса.
del_key
def __del_key(self, _address: str):
ind = self.__get_ind_by_address(_address)
self.keys[ind] = rsa.key.PublicKey
self.my_keys[ind] = rsa.key.PrivateKey
check функции
Эти функции направлены на получение информации о данных на сервере.
Функция check_request проверяет наличие входящих сообщение от указанного адреса и возвращает его в виде True при наличии или False при отсутствии.
check_request
def check_request(self, _address: str):
return bool(self.incoming_requests.get(_address))
Функции check_address проверяет есть ли указанный адрес среди подключённых пользователей или нет и возвращает True, если он есть или False, если его нет.
check_address
def check_address(self, _address: str):
return True if _address in self.clients_ip else False
get функции
Функция get_free_socket только для внутренней работы сервера и возвращает индекс свободного соккета, если такие есть, иначе ничего.
get_free_socket
def __get_free_socket(self):
for i in range(len(self.socket_busy)):
if not self.socket_busy[i]:
return i
return None
Функция get_ind_by_address тоже только для внутренней работы, она возвращает номер соккета, к которому подключён данный адрес или ничего, если адрес никуда не подключён.
get_ind_by_address
def __get_ind_by_address(self, _address: str):
for i in range(len(self.clients_ip)):
if self.clients_ip[i] == _address:
return i
else:
return None
И последняя функция get_request возвращает первое сообщение от указанного адреса и удаляет его из сервера. Она выкинет ошибку, если сообщений нет вообще.
get_request
def get_request(self, _address: str):
data = self.incoming_requests[_address][0]
self.incoming_requests[_address] = [self.incoming_requests[_address][i]
for i in range(1, len(self.incoming_requests[_address]))]
return data
server control функции
Это основные функции работы сервера, в них заключается логика работы сервера.
Одна из самых важных функций — create_session — она устанавливает соединение с указанным адресом. Здесь осуществляется проверка наличия адреса в в чёрном списке, загруженность соккетов, осуществляется обмен ключами шифрования при успешном подключении и запускается цикл прослушивания соккета, который получает сообщени я и работает с ними.
create_session
def create_session(self, _address: str):
self.log.save_data("Creating session with {}".format(_address))
ind = self.__get_free_socket()
if _address in self.blacklist:
self.log.save_data("{} in blacklist".format(_address))
return
if ind is None:
self.log.save_data("All sockets are busy, can`t connect to {}".format(_address))
return
try:
self.__add_user(_address)
thread = Thread(target=self.__connect, args=(_address, 1))
thread.start()
thread.join(0)
connection, address = self.server_socket.accept()
connection.settimeout(0.2)
except OSError:
self.log.save_data("Failed to create session with {}".format(_address))
self.__del_user(_address)
return
my_key = rsa.newkeys(512)
self.raw_send(_address, my_key[0].save_pkcs1())
key = connection.recv(162).decode()
self.clients_logs[ind].save_data("from {}: {}".format(_address, key))
key = rsa.PublicKey.load_pkcs1(key)
self.__add_keys(_address, key, my_key[1])
while self.running and self.socket_busy[ind]:
try:
data = connection.recv(2048)
except socket.timeout:
continue
except OSError:
self.close_connection(_address)
return
if data:
data = rsa.decrypt(data, self.my_keys[ind])
self.__add_request(_address, data)
try:
self.close_connection(_address)
except TypeError or KeyError:
pass
Функция connect осуществляет подключение к пользователю с указанным адресом и возвращает True при успехе или False при неудаче. Использовать её стоит только внутри сервера.
connect
def __connect(self, _address: str, *args):
ind = self.__get_ind_by_address(_address)
try:
self.client_sockets[ind].connect((_address, self.port))
self.socket_busy[ind] = True
return True
except OSError:
return False
Функция close_connection закрывает соединение с указанным адресом.
close_connection
def close_connection(self, _address: str):
ind = self.__get_ind_by_address(_address)
self.__del_key(_address)
self.__reload_socket(ind)
self.__del_user(_address)
Функция kill_server полностью выключает сервер.
kill_server
def kill_server(self):
self.running = False
sleep(1)
self.server_socket.close()
self.log.kill_log()
for i in self.client_sockets:
i.close()
for i in self.clients_logs:
try:
i.kill_log()
except TypeError:
pass
И последняя функция reload_socket, предназначенная для использования внутри самого сервера, перезагружает сокет с указанным индексом.
reload_socket
def __reload_socket(self, _ind: int):
self.client_sockets[_ind].close()
self.client_sockets[_ind] = socket.socket()
self.socket_busy[_ind] = False
Другие функции
Функция bool возвращает True, если есть хоть какое-нибудь подключение, или False, если таких нет.
bool
def __bool__(self):
for i in self.clients_ip:
if i != "":
return True
return False
Функция len возвращает количество подключённых к серверу клиентов.
len
def __len__(self):
num = 0
for i in self.clients_ip:
if i != "":
num += 1
return num
Также стоить написать небольшой лог для сервера, который будет документировать процесс работы сервера и процесс обмена сообщения между пользователями. Стоит сказать, что открытие и закрытие файла при каждой записи необходимо в данном случае. Так как при вылете сервера или программы, где он задействован может потребоваться проверить лог работы сервера и данные сохранятся только в таком случае.
А теперь разберём функции на пальцах, тем более здесь ничего сложного нет.
Для инициализации сервера потребуется только имя файла. Сначала мы попробуем открыть файл на до запись, но если такого файла нет, то создадим его. Сразу же в файл запишем время старта лога.
init
def __init__(self, _name: str):
self.name = _name
try:
self.file = open(_name, "a")
except FileNotFoundError:
self.file = open(_name, "w")
self.save_data("Log started at " + str(datetime.datetime.now()))
self.file.close()
Функция save_data сохраняет в файл указанное сообщение.
save_data
def save_data(self, _data: str):
self.file = open(self.name, "a")
self.file.write("{}\n".format(_data))
self.file.close()
Статическая функция read_and_return_list не требует объекта класса для использования, но требует для своей работы имя файла из которого будет взята вся информация и возвращена в виде листа.
read_and_return_list
@staticmethod
def read_and_return_list(_name: str):
try:
file = open(_name, "r")
except FileNotFoundError:
return []
data = file.read()
return data.split("\n")
И последняя функция kill_log записывает в файл время остановки лога и закрывает файл.
kill_log
def kill_log(self):
self.file = open(self.name, "a")
self.save_data("Log stopped at {}\n".format(datetime.datetime.now()))
self.file.close()
Написать сервер для одноранговой сети не сложно, но, с другой стороны, есть куда двигаться. Можно реализовать отправку файлов и изображений, скачивание их по частям сразу от нескольких пользователей. Можно усовершенствовать лог или добавить шифрование на отправку ключей для шифрования и дешифрования.
Если есть какие-либо предложения по этому поводу или варианты улучшения кода буду рад почитать в комментариях.
Python. Давайте создадим простой HTTP-сервер
Веб-серверы есть везде.
Черт возьми, вы взаимодействуете с одним прямо сейчас!
Независимо от того, какой вы разработчик программного обеспечения, в какой-то момент вашей карьеры вам придется взаимодействовать с веб-серверами. Может быть, вы создаете сервер API для бэкэнда. Или, может быть, вы просто настраиваете веб-сервер для своего сайта.
В этой статье мы расскажем, как создать самый простой http веб-сервер на Python.
Но поскольку мы хотим убедиться, что вы понимаете, что мы создаем, мы сначала дадим обзор о том, что такое веб-серверы и как они работают.
Если вы уже знаете, как работают веб-серверы, вы можете сразу перейти к этому разделу.
- Что такое HTTP-сервер?
- Адрес сокета TCP
- Создайте простой файл HTTP
- Создать HTTP веб-сервер
Что такое HTTP-сервер?
Веб-сервер HTTP – это не что иное, как процесс, который выполняется на вашем компьютере и выполняет ровно две вещи:
1- Прослушивает входящие HTTP-запросы на определенный адрес сокета TCP (IP-адрес и номер порта, о которых мы расскажем позже)
2- Обрабатывает этот запрос и отправляет ответ обратно пользователю.
Но что на самом деле происходит под капотом?
На самом деле много чего происходит, и мы могли бы посвятить целую статью, чтобы объяснить магию, как это произошло.
Но для простоты мы отвлечемся от некоторых деталей и расскажем об этом на очень высоком уровне.
На высоком уровне, когда вы набираете www.yandex.ru в своем браузере, ваш браузер создаст сетевое сообщение, называемое HTTP-запросом.
Этот запрос будет распространяться на компьютер yandex, на котором работает веб-сервер. Этот веб-сервер перехватит ваш запрос и обработает его, отвечая HTML-кодом домашней страницы yandex.
Наконец, ваш браузер отображает этот HTML на экране, и это то, что вы видите на своем экране.
Каждое взаимодействие с домашней страницей yandex после этого (например, когда вы нажимаете на ссылку) инициирует новый запрос и ответ точно так же, как первый.
Повторим еще раз: на машине, которая получает запрос http, работает программный процесс, называемый веб-сервером. Этот веб-сервер отвечает за перехват этих запросов и их обработку соответствующим образом.
Хорошо, теперь, когда вы знаете, что такое веб-сервер и какова его функция, вам может быть интересно, как запрос в первую очередь достигает машины yandex?
Хороший вопрос!
Позвольте нам объяснить, как, но опять же … на высоком уровне.
Адрес сокета TCP
Любое http-сообщение (будь то запрос или ответ) должно знать, как добраться до места назначения.
Чтобы достичь места назначения, каждое http-сообщение содержит адрес, называемый адресом TCP назначения.
И каждый TCP-адрес состоит из IP-адреса и номера порта.
Мы знаем, что все эти аббревиатуры (TCP, IP и т. д.) могут быть ошеломляющими, если ваши сетевые знания не сильны.
Так где же этот адрес, когда все, что вы сделали, и набрали www.yandex.ru в вашем браузере?
Ну, это доменное имя преобразуется в IP-адрес через большую распределенную базу данных, называемую DNS.
Хотите проверить, что это за IP-адрес?
Легко! Зайдите в свой терминал и сделайте следующее:
$ host yandex.ru Yandex.ru has address 77.88.55.66 Yandex.ru has address 5.255.255.70 Yandex.ru has address 77.88.55.70 Yandex.ru has address 5.255.255.60 Yandex.ru has IPv6 address 2a02:6b8:a::a Yandex.ru mail is handled by 10 mx.Yandex.ru.
Как видите, как DNS переведет yandex.ru на любой из указанных выше адресов.
Один только IP-адрес позволит HTTP-сообщению поступить на нужный компьютер, но вам все равно нужен номер порта, чтобы HTTP-запрос поступил именно на веб-сервер.
Другими словами, веб-сервер – это обычное сетевое приложение, которое прослушивает определенный порт.
И HTTP-запрос ДОЛЖЕН быть адресован этому порту.
Так где же номер порта при вводе www.yandex.ru ?
По умолчанию номер порта равен 80 для http и 443 для https, поэтому даже если вы не указали номер порта явно, он все еще там.
И если веб-сервер прослушивает номер порта не по умолчанию (ни 80, ни 443), вы должны явно указать номер порта следующим образом:
www.yandex.ru:445
К настоящему времени у вас должна быть вся необходимая информация для создания http-сервера на Python.
Так что без дальнейших церемоний, давайте начнем.
Создайте простой файл HTML
Вот что мы хотим сделать.
Мы хотим создать простой http-сервер, который обслуживает статическую HTML-страницу.
Давайте создадим нашу HTML-страницу.
<html> <head> <title>Python-это потрясающе!</title> </head> <body> <h2>yandex</h2> <p>Поздравляю! Сервер HTTP работает!</p> </body> </html>
Теперь сохраните этот файл как index.html.
Теперь, для обслуживания веб-страницы, следующим шагом является создание веб-сервера, который будет обслуживать эту HTML-страницу.
Создать HTTP веб-сервер
Чтобы создать веб-сервер в Python 3, вам нужно импортировать два модуля: http.server и socketserver
Обратите внимание, что в Python 2 был модуль с именем SimpleHTTPServer. Этот модуль был объединен с http.server в Python 3
Давайте посмотрим на код для создания http-сервера
import http.server import socketserver PORT = 8080 Handler = http.server.SimpleHTTPRequestHandler with socketserver.TCPServer(("", PORT), Handler) as httpd: print("serving at port", PORT) httpd.serve_forever()
Просто так у нас есть функциональный http-сервер.
Теперь давайте разберем этот код построчно.
Во-первых, как мы упоминали ранее, веб-сервер – это процесс, который прослушивает входящие запросы на определенный TCP-адрес.
И, как вы уже знаете, TCP-адрес идентифицируется по IP-адресу и номеру порта.
Во-вторых, веб-сервер также должен знать, как обрабатывать входящие запросы.
Эти входящие запросы обрабатываются специальными обработчиками. Вы можете думать о веб-сервере как о диспетчере, поступает запрос, http-сервер проверяет запрос и отправляет его назначенному обработчику.
Конечно, эти обработчики могут делать все что угодно.
Но что вы думаете, какой самый основной обработчик?
Ну, это будет обработчик, который просто обслуживает статический файл.
Другими словами, когда мы заходим на yandex.ru, веб-сервер на другом конце отправляет обратно статический HTML-файл.
Это на самом деле то, что мы пытаемся сделать.
И это, и есть то, что является http.server.SimpleHTTPRequestHandler : простой обработчик HTTP-запросов, который обслуживает файлы из текущего каталога и любых его подкаталогов.
Класс socketserver.TCPServer
Теперь поговорим о классе socketserver.TCPServer.
Экземпляр TCPServer описывает сервер, который использует протокол TCP для отправки и получения сообщений (http – это протокол прикладного уровня поверх TCP).
Чтобы создать экземпляр TCP-сервера, нам нужны две вещи:
- TCP-адрес (IP-адрес и номер порта)
- Обработчик
socketserver.TCPServer(("", PORT), Handler)
Как видите, TCP-адрес передается в виде кортежа (IP-адрес, номер порта)
Передача пустой строки в качестве IP-адреса означает, что сервер будет прослушивать любой сетевой интерфейс (все доступные IP-адреса).
А поскольку PORT хранит значение 8080, сервер будет прослушивать входящие запросы на этот порт.
Для обработчика мы передаем простой обработчик, о котором мы говорили ранее.
Handler = http.server.SimpleHTTPRequestHandler
Ну, а как насчет serve_forever?
serve_forever – это метод в экземпляре TCPServer, который запускает сервер и начинает прослушивать и отвечать на входящие запросы.
Круто, давайте сохраним этот файл как server.py в том же каталоге, что и index.html, потому что по умолчанию SimpleHTTPRequestHandler будет искать файл с именем index.html в текущем каталоге.
В этом каталоге запустите веб-сервер:
$ python server.py serving at port 8080
Благодаря этому теперь у вас есть HTTP-сервер, который прослушивает любой интерфейс на порте 8080 и ожидает входящие HTTP-запросы.
Пришло время для забавных вещей!
Откройте браузер и введите localhost:8080 в адресной строке.
Потрясающие! Похоже, все работает нормально.
Но что такое localhost ?
localhost – это имя хоста, которое означает этот компьютер. Он используется для доступа к сетевым службам, работающим на хосте, через петлевой сетевой интерфейс.
А поскольку веб-сервер прослушивает любой интерфейс, он также прослушивает интерфейс обратной связи.
Вы хотите знать, какой IP-адрес соответствует localhost?
Введите следующее:
$ host localhost localhost has address 127.0.0.1 localhost has IPv6 address ::1 Host localhost not found: 3(NXDOMAIN)
Фактически вы можете полностью заменить localhost на 127.0.0.1 в вашем браузере, и вы все равно получите тот же результат.
Одно последнее слово
На самом деле вы можете запустить веб-сервер с python, даже не создавая никаких скриптов.
Просто зайдите в свой терминал и сделайте следующее (но убедитесь, что вы на Python 3)
python -m http.server 8080
По умолчанию этот сервер будет прослушивать все интерфейсы и порт 8080.
Если вы хотите прослушать определенный интерфейс, сделайте следующее:
python -m http.server 8080 --bind 127.0.0.1
Также начиная с Python 3.7, вы можете использовать флаг –directory для обслуживания файлов из каталога, который не обязательно является текущим каталогом.
Таким образом, теперь возникает вопрос: зачем вам когда-либо писать сценарий, когда вы можете просто вызывать сервер из терминала?
Хорошо, помните, что вы используете SimpleHTTPRequestHandler. Если вы хотите создать свои собственные обработчики (что вы, вероятно, захотите), то вы не сможете сделать это из терминала.
Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.
реализуем веб-сервер своими руками / Песочница / Хабр
Intro
Недавно столкнулся с интересной задачей: нужно было написать программу, которая откликается на указанном порту по протоколу http и генерирует гиперссылку, составленную определенным образом. Приложение должно было быть независимым и поэтому реализовать его просто как веб-страницу не представлялось возможным. Оказалось, что реализовать веб-сервер на Питоне вообще не составляет никакого труда, потому как все необходимое уже присутствует в стандартной библиотеке. Но все по порядку…
Пакет http
Как выяснилось, сам интерпретатор уже имеет в своем составе пакет http, в котором сосредоточены все функции работы с этим протоколом: как серверные, так и клиентские, в виде отдельных модулей. Так как мы пишем сервер, то нас в первую очередь интересует модуль, отвечающий именно за функции обслуживания http-запросов: в Python 3 они объединены в один модуль под названием http.server, в Python 2 эти функции разнесены по трем модулям: BaseHTTPServer, CGIHTTPServer и SimpleHTTPServer.
Обработчик запросов: BaseHTTPRequestHandler
Для реализации простого сервера будет достаточно использовать «класс-заготовку» — BaseHTTPRequestHandler. Этот класс может использоваться как базовый для реализации собственного обработчика запросов, действующего в составе сервера HTTP. После того как клиент установит соединение и HTTP-заголовки его запроса будут проанализированы, выполняется попытка вызвать метод вида do_REQUEST, имя которого определяется исходя из типа запроса: для обработки GET-запроса типа будет вызван метод do_GET(), для обработки POST-запроса – метод do_POST() и так далее. По умолчанию этот класс ничего не делает, и предполагается, что эти методы должны быть переопределены в подклассах. В простейшем случае требуется реализовать лишь метод do_GET():
from BaseHTTPServer import BaseHTTPRequestHandler,HTTPServer
class HttpProcessor(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header(‘content-type’,’text/html’)
self.end_headers()
self.wfile.write(«hello !»)
Теперь необходимо создать объект класса HTTPServer, передав ему в качестве параметров IP-адрес и порт, на котором будет работать http-сервер, а также наш класс-потомок BaseHTTPRequestHandler, который собственно и будет отвечать за обработку запросов к серверу:
serv = HTTPServer(("localhost",80),HttpProcessor)
serv.serve_forever()
Первой строкой мы создаем экземпляр HTTPServer, указывая ему, по какому адресу и с помощью чего обрабатывать наши http-запросы, затем, вызовом метода serve_forever(), мы указываем интерпретатору перманентно запустить веб-сервер — приложение будет работать до тех пор, пока его работа не будет прервана путем нашего вмешательства. Убедиться в его работоспособности можно, обратившись через браузер по тому адресу, который мы забиндили при написании скрипта: нашему взору предстанет веб-страница с единственной фразой — «hello».
Как это работает
Для ясности, рассмотрим текст метода do_GET():
Перво-наперво, при успешном соединении, необходимо отправить браузеру код ответа -информацию о том, что запрашиваемая страница была обнаружена (код 200: HTTP/ok):
self.send_response(200)
Затем необходимо отправить http-заголовки, для чего используется метод send_header(), а также эскейп-посдедовательность (\r\n\r\n), которая свидетельствует о завершении заголовка ответа:
self.end_headers()
Так как мы установили в заголовках тип содержимого ответа: Content-type: text/html, то весь текст, выведенный нашим методом do_GET(), будет интерпретирован клиентом как HTML-разметка. Также следует обратить внимание на атрибут wfile класса BaseHTTPRequestHandler
. Он представляет собой объект, который реализует поток сервера, транслируемый на клиент. Любой вывод в браузер осуществляется методом wfile.write(‘something’).
В заключение
Предложенный способ реализации может применяться в качестве независимого http-сервера «на скорую руку», легко переносимого (реализован один файлом) и работающего везде, где работает Python. Отмечу лишь, что в данном примере использован интерпретатор Python 2.7. В реализации на Python 3 будут некоторые отличия. Более подробную информацию о пакете http можно получить из официальной документации по языку Python.
5 способов сделать Python-сервер на Raspberry Pi. Часть 1 / Хабр
Привет, Хабр.
Сегодня в большом числе проектов домашней (и не только) автоматизации используется Raspberry Pi. При этом достаточно удобно иметь не только прямой доступ к устройству, но и использовать браузер — это позволяет выполнять необходимые действия и с компьютера, и с мобильного телефона, и даже удаленно из любой точки мира.
Допустим, у нас уже есть супер Python-программа, делающая что-то очень важное, от мигания светодиодом до управления «умным домом» или хотя бы кормушкой для кота. Я покажу разные способы, от простого к сложному, как сделать web-доступ к такому приложению, добавив немного кода.
Статья расчитана для начинающих, профи вряд ли найдут здесь что-то кардинально новое, ну а новичкам в Linux надеюсь, будет полезно. Для тех кому интересно, продолжение под катом.
Примечание: эта статья является своего рода «экспериментом», как-то в комментариях жаловались что на Хабре недостаточно статей для начинающих. Я попытался восполнить пробел, ну а по оценкам будет видно, имеет смысл продолжать в таком формате или нет.
Итак, приступим.
Настройка Raspberry Pi
Будем надеятся, что у читателя есть Raspberry Pi, которая подключена к домашней сети через WiFi или Ethernet, и читатель знает что такое IP адрес и как зайти удаленно на Raspberry Pi через SSH при помощи putty. Мы будем рассматривать так называемую headless-конфигурацию — без клавиатуры и монитора. Но перед тем, как делать что-то с Raspberry Pi, пара небольших лайфхаков.
Совет N1. Чтобы что-то удаленно делать с Raspberry Pi, на нем нужно настроить SSH, а по умолчанию он выключен. Можно пойти традиционным способом, и запустить стандартный конфигуратор, но можно сделать проще — после записи образа диска достаточно создать пустой файл ssh (без расширения) в корне SD-карты. Дальше, после загрузки Raspberry Pi, SSH будет сразу активен.
Чтобы зайти удаленно на устройство, нужно узнать IP-адрес Raspberry Pi. Для этого достаточно открыть контрольную панель своего маршрутизатора, найти там список DHCP-клиентов, скопировать оттуда нужный IP-адрес (например, это будет 192.168.1.102), и ввести команду putty.exe [email protected] (для Windows) или ssh [email protected] для Linux или OSX.
Однако, IP-адреса могут меняться, например после перезагрузки маршрутизатора, это не всегда удобно. Из этого следует Совет N2 — настроить статический IP-адрес. Для этого на Raspberry Pi выполняем команду sudo nano /etc/dhcpcd.conf, и вводим следующие настройки:
interface eth0
static ip_address=192.168.1.152/24
static routers=192.168.1.1
static domain_name_servers=192.168.1.1 8.8.8.8
Если нужен адрес WiFi, то интерфейс будет wlan0, если Ethernet то eth0. IP-адреса разумеется, нужно тоже подставить свои. После перезагрузки убеждаемся что IP-адрес правильный, введя команду ifconfig.
Теперь все готово, можем приступать к Python. Все примеры даны для Python 3.7, т.к 2.7 уже давно устарел, и поддерживать его бесмысленно. Но при небольших изменениях кода все заработает и там, если нужно. Кстати, язык Python является кроссплатформенным — это значит что весь приведенный ниже код можно запустить и на Windows и на OSX, ну и разумеется, на Raspberry Pi. Из этого следует Совет N3 — отлаживать программу можно и на обычном ПК, а уже готовую версию заливать на Raspberry Pi. Возможно, придется лишь сделать функции-обертки для методов GPIO, все остальное будет работать.
Итак, наша задача — обеспечить доступ к приложению через обычный браузер. Ибо это стильно-модно-молодежно, ну и «интернет вещей» это наше все.
Способ 1: командная строка
Самый простой способ, не требующий вообще никакого программирования.
Выбираем нужную папку на Raspberry Pi, и вводим команду:
python3 -m http.server 5000
Все, на Raspberry Pi работает файловый сервер! Достаточно зайти на страницу http://192.168.1.102:5000 и мы увидим наши файлы в браузере:
Это достаточно удобно, если нужно открыть удаленный доступ к каким-либо файлам с минимумом затраченных сил. Можно также ввести команду sudo python3 -m http.server 80 и запустить сервер со стандартным 80-м портом, это позволит не указывать порт в адресной строке браузера.
Кстати, если мы хотим, чтобы сервер работал и после закрытия терминала, можно использовать команду sudo nohup python3 -m http.server 80 & — это запустит процесс в фоне. Убить такую программу можно перезагрузкой, или вводом в командной строке команды sudo killall python3.
Способ 2: SimpleHTTPServer
Мы можем довольно просто интегрировать такой же сервер в нашу программу на Python, для этого достаточно запустить его отдельным потоком при старте программы. Теперь, нам не надо возиться с командной строкой, пока программа запущена, сервер будет работать.
import http.server
import socketserver
from threading import Thread
import os
def server_thread(port):
handler = http.server.SimpleHTTPRequestHandler
with socketserver.TCPServer(("", port), handler) as httpd:
httpd.serve_forever()
if __name__ == '__main__':
port = 8000
print("Starting server at port %d" % port)
os.chdir("/home/pi/Documents")
Thread(target=server_thread, args=(port,)).start()
Команда os.chdir является опциональной, если мы хотим предоставить доступ из сервера к какой-то другой папке, кроме текущей.
Способ 3: HTTPServer
Это уже полноценный web-сервер, способный обрабатывать GET и POST-запросы, возвращать разные данные и пр. Но и кода разумеется, понадобится больше.
Рассмотрим минимально работающий вариант сервера:
from http.server import BaseHTTPRequestHandler, HTTPServer
html = "<html><body>Hello from the Raspberry Pi</body></html>"
class ServerHandler(BaseHTTPRequestHandler):
def do_GET(self):
if self.path == "/":
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
self.wfile.write(html.encode('utf-8'))
else:
self.send_error(404, "Page Not Found {}".format(self.path))
def server_thread(port):
server_address = ('', port)
httpd = HTTPServer(server_address, ServerHandler)
try:
httpd.serve_forever()
except KeyboardInterrupt:
pass
httpd.server_close()
if __name__ == '__main__':
port = 8000
print("Starting server at port %d" % port)
server_thread(port)
Запускаем браузер, и видим в нем нашу HTML-страницу:
Данный сервер несложно научить отдавать файлы, например изображения.
Добавим в HTML тег img:
html = '<html><body><h4>Hello from the Raspberry Pi</h4><img src="raspberrypi.jpg"/></body></html>'
Исходный файл «raspberrypi.jpg» разумеется, должен лежать в папке с программой. Добавим в функцию do_GET возможность получения файлов:
def do_GET(self):
print("GET request, Path:", self.path)
if self.path == "/":
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
self.wfile.write(html.encode('utf-8'))
elif self.path.endswith(".jpg"):
self.send_response(200)
self.send_header('Content-type', 'image/jpg')
self.end_headers()
with open(os.curdir + os.sep + self.path, 'rb') as file:
self.wfile.write(file.read())
else:
self.send_error(404, "Page Not Found {}".format(self.path))
Запускаем сервер, и видим соответствующую картинку:
Вряд ли такой сервер выиграет конкурс веб-дизайна, но он вполне работает. Сервер несложно заставить отдавать более полезные данные, например возвращать информацию о работе программы. Для примера добавим обработчик для новой функции status:
import psutil
import json
def cpu_temperature():
return psutil.sensors_temperatures()['cpu-thermal'][0].current
def disk_space():
st = psutil.disk_usage(".")
return st.free, st.total
def cpu_load() -> int:
return int(psutil.cpu_percent())
def ram_usage() -> int:
return int(psutil.virtual_memory().percent)
def do_GET(self):
...
elif self.path == "/status":
self.send_response(200)
self.send_header('Content-Type', 'application/json')
self.end_headers()
health = {'CPUTemp': cpu_temperature(), 'CPULoad': cpu_load(), "DiskFree": disk_space()[0], "DiskTotal": disk_space()[1], "RAMUse": ram_usage()}
self.wfile.write(json.dumps(health).encode('utf-8'))
Теперь мы можем открыть в браузере ссылку http://192.168.1.102:5000/status и увидеть текущие параметры системы:
Кстати, как можно видеть, мы отдаем данные в формате JSON, что позволит использовать их для каких-то других запросов.
Заключение
Все задуманное в одну часть не влезло. Вторая часть доступна по ссылке.
Важно: меры безопасности
Если для Raspberry Pi будет использоваться внешний IP-адрес, обязательно стоит помнить о мерах безопасности. Может показаться что ваш мини-сервер никому не нужен, однако сейчас не составляет труда пакетно просканировать все диапазоны IP-адресов (как пример, Украина, Австрия) и найти все доступные устройства. Так что обязательно стоит поменять пароль на Raspberry Pi, и не стоит хранить на устройстве какую-либо конфиденциальную информацию (папки Dropbox, имена/пароли захардкоженные в скриптах, фото и пр).
PS: Для понимания картины добавил опрос
Веб-сервер на Python
Вы здесь:
Главная — Python — Основы Python — Веб-сервер на Python
Веб сервер представляет собой программу, ожидающую подключения и способную работать как на локальном компьютере так и удаленном сервере. Как правило сервер запускается в определенном каталоге, удаленно или же локально(«localhost»), и по выделенному адресу в памяти (порту). Обычно номер порта 8000, в диапазоне от 1000 до 60 000. После запуска сервера в браузере, можно видеть структуру папки, являющейся местом его запуска. Если создать там же, файл index.html c корректной HTML разметкой, то при обновлении страницы браузера или же запуске программы сервера, на экране появится веб страница, соответствующая коду HTML. Для запуска нашего сервера мы используем библиотеку http языка Python. Для начала создадим отдельную папку, которая будет вмещать все необходимые файлы. Поместим в нее файл web_server.py, со следующим кодом:
from http.server import HTTPServer, CGIHTTPRequestHandler
server_address = ("",8800)
httpd = HTTPServer(server_address, CGIHTTPRequestHandler)
httpd.serve_forever()
В том же каталоге создадим файл index.html, с кодом:
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<title>Page Title</title>
</head>
<body>
<h2>Считывание из формы</h2>
<p>This is a paragraph.</p>
<hr>
<form method="POST" action="cgi-bin/my_action.py">
<table>
<TR>
<TH align="right"> введите имя</TH>
<TD ><input type="text" name="user"></TD>
</TR>
<TH align="right"> введите ваш возраст</TH>
<TD ><input type="text" name="age"></TD>
<TR>
<TH colspan="2" align="right"></TH>
<TD ><input type="submit" value="Send"></TD>
</TR>
</table>
</form>
</body>
</html>
Здесь, с помощью input, мы создали поля для ввода данных определив их тип как text,
а кнопка отправки данных определена типом submit. Тег table создает таблицу, а теги TR,TH,TD упорядочивают ее. В теге form, указан скрипт cgi-bin/my_action.py,
который будет выполняться в ответ на нажатие submit. Обычно исполняемый скрипт при запуске на localhost, хранится в папке с названием cgi-bin. Давайте создадим ее внутри рабочего каталога.
Скрипт на Python
При написании скрипта на Python, следует сохранять его в формате UTF -8 без BOM, если вы работаете в Notepade, и добавить в первые строки:
#! /usr/bin/python3
# -*- coding: UTF-8 -*- # обязательная часть кода
print("Content-type: text/html\n") # обязательная часть кода - заголовки ответа
Первая строка нужна для работы в Linux, Windows ее игнорирует, а вторая для избежания ошибок кодировки файла. Скрипт следует прежде всего выполнить отдельно, чтобы убедиться в его корректном выполнении, а затем вызывать его на сервере. И так создадим в папке cgi-bin, файл my_action.py, добавим код выше, следуя инструкции, :
#! /usr/bin/python3
# -*- coding: UTF-8 -*-
import cgi, sys
print("Content-type: text/html\n")
form = cgi.FieldStorage() # получение данных из формы
html = """
<TItle>my_action.py</TItle>
<h2>Hello World!</h2>
<HR>
<h5>%s</h5>
<h5>%s</h5>
</HR>"""
line1 = 'Hello, %s.' % form['user'].value
line2 = 'Your age, %s.' % form['age'].value
print(html%(line1, line2)) # вывод полученных данных
Такого рода код в веб программировании называется CGI скриптом, и служит для взаимодействия сервера с веб клиентом т.е. браузером. Он во первых выводит html разметку, во вторых обрабатывает введенные пользователем данные модулем cgi, который получает данные из формы form. Таким образом CGI скрипт является основой клиент-серверного взаимодействия.
-
Создано 15.09.2020 12:02:35 -
Михаил Русаков
Предыдущая статья Следующая статья
Копирование материалов разрешается только с указанием автора (Михаил Русаков) и индексируемой прямой ссылкой на сайт (http://myrusakov.ru)!
Добавляйтесь ко мне в друзья ВКонтакте: http://vk.com/myrusakov.
Если Вы хотите дать оценку мне и моей работе, то напишите её в моей группе: http://vk.com/rusakovmy.
Если Вы не хотите пропустить новые материалы на сайте,
то Вы можете подписаться на обновления: Подписаться на обновления
Если у Вас остались какие-либо вопросы, либо у Вас есть желание высказаться по поводу этой статьи, то Вы можете оставить свой комментарий внизу страницы.
Порекомендуйте эту статью друзьям:
Если Вам понравился сайт, то разместите ссылку на него (у себя на сайте, на форуме, в контакте):
-
Кнопка:
<a href=»https://myrusakov.ru» target=»_blank»><img src=»https://myrusakov.ru/images/button.gif» alt=»Как создать свой сайт» /></a>Она выглядит вот так:
-
Текстовая ссылка:
<a href=»https://myrusakov.ru» target=»_blank»>Как создать свой сайт</a>Она выглядит вот так: Как создать свой сайт
- BB-код ссылки для форумов (например, можете поставить её в подписи):
[URL=»https://myrusakov.ru»]Как создать свой сайт[/URL]
Примеры простейших серверов на Python
Примеры простейших серверов на Python
Коротко зачем
Этот набор примеров серверов я написал, преследуя две основные цели.
Во-первых, для себя. Иногда надо иметь небольшой сервер
для тестов или для быстрых экспериментов. Этот сервер должен
быть просто скелетиком сервера. От него не требуется устойчивости или
производительности, но от него требуется простота кода и
вполне конкретный дизайн. И, конечно, надо чтобы он работал,
а не был набором функций «сделай сам».
Таковыми являются эти примеры.
Во-вторых, чтобы скомпенсировать
недостатки многочисленных источников информации на эту тему.
Основные преимущества таковы.
- Каждый пример является полноценной программой, которую можно запустить.
- Примеры не используют какие-либо фреймворки и демонстрируют
самые базовые механизмы. Зная эти механизмы легко можно
освоить и разумно использовать фреймворк. - Примеры не перегружены деталями. Сетевое программирование
всегда связно с обработкой множества ситуаций, состояний, исключений…
Здесь им уделяется минимальное внимание, чтобы была видна именно
структура приложения. - Чтобы не было скучно, это написано на Python 3. Надеюсь, в далёком
будущем, где живёшь ты, читатель, Python 3 уже восторжествовал 🙂
Осторожно! Недостатки
Перечисленные достоинства приводят к появлению соответствующих недостатков.
Приведённый здесь код нельзя использовать «в бою».
Тут не отслеживаются многие, даже самые примитивные
и распространённые, аварийные ситуации типа
«мы пытаемся передать ответ клиенту, а он уже отключился»
(Broken pipe).
Если вы ничего не знаете о серверах и хотите написать сервер,
мне кажется оптимальным такой подход:
- если требуется, почитать что-нибудь очень обзорное,
- почитать эти заметки, чтобы понимать, как что-то работает в принципе,
- посмотреть в сторону стандартных библиотек, скорее всего, там
есть всё, что вам нужно, - если вы видите, что ваша задача на столько не стандартна
или имеются требования, для удовлетворения которых стандартная
библиотека требует переопределить все методы
(например, писать все события в graphite), то имеет смысл
взять за основу стандартную библиотеку и взяться писать свою
реализацию сервера. При реализации своего сервера, обязательно
изучите реализацию хотя быasyncore.py
. Хотя бы на предмет
обработки исключений.
Сами сервера
Предложения и замечания принимаются.
Отправить
Очень простой чат(клиент/сервер) на Python
В этой статье мы напишем очень простой консольный чат на популярном языка Python. Состоять он будет из двух частей. Первая чать это сервер, куда будут приходить сообщения клиентов которые подключены к серверу. Втора чать это клиент, которые отправляет сообщения серверу и получает сообщения от сервера.
Постановка задачи.
- Написать сервер для приема сообщений от клиента и отправки сообщений всем остальным клиентам подключенным к серверу. Будем использовать протокол TCP/IP.
- Собственно сам клиент. Который коннектится к серверу по TCP/IP. Отправляет и получает сообщения от сервера.
- Ну и реализуем какое нибудь простое шифрование. Что бы сообщения могли читать только клиенты.
Часть первая. Сервер.
Первым делом нам надо создать сокет, который будет принимать соединения скажем на порту 5050 . Для работы с сокет в Python есть модуль который так и называется socket. Подключим его :
import socket
Создадим сам сокет:
sock = socket.socket (socket.AF_INET, socket.SOCK_DGRAM)
socket.AF_INET — для сокета используем IPv4 . socket.SOCK_DGRAM — тип сокета. Датаграммный сокет — это сокет, предназначенный для передачи данных в виде отдельных сообщений (датаграмм). По сравнению с потоковым сокетом, обмен данными происходит быстрее, но является ненадёжным: сообщения могут теряться в пути, дублироваться и переупорядочиваться. Датаграммный сокет допускает передачу сообщения нескольким получателям (multicasting) и широковещательную передачу (broadcasting).
Теперь свяжем сокет с адресом(интерфейсом) и портом :
sock.bind (('',5050))
Пустые кавычки значат что сокет слушает все доступные интерфейсы.
Теперь нам надо как то принимать сообщения. Нам совершенно все равно от кого и что получать. Задача получить и отправить остальным известным клиентам. По этому, мы будем использовать функцию socket.recvfrom(bufsize) которая нам вернет данные и адрес сокета с которого получены эти данные.
data , addres = sock.recvfrom(1024) # Буфер в байтах
Для отправки данных будем использовать функцию socket.sendto( bytes, address ) :
sock.sendto(data,addres)
Итог у нас такой :
import socket sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) sock.bind (('94.250.252.115',5050)) client = [] # Массив где храним адреса клиентов print ('Start Server') while 1 : data , addres = sock.recvfrom(1024) print (addres[0], addres[1]) if addres not in client : client.append(addres)# Если такого клиента нету , то добавить for clients in client : if clients == addres : continue # Не отправлять данные клиенту, который их прислал sock.sendto(data,clients)
Клиентская часть.
С клиентом немного все посложней. Так как это чат, нам надо получать и отправлять сообщения одновременно. Или не зависимо друг от друга. Для этого нам потребуется многопоточное выполнение нашего кода. Для этого мы будем использовать модуль threading
import threading
Первым делом создадим функцию которая будет получать сообщения от сервера:
def read_sok():
while 1 :
data = sor.recv(1024)
print(data.decode('utf-8'))
Теперь нам надо создать поток и запустить в нем эту функцию:
potok = threading.Thread(target= read_sok)
potok.start()
Теперь весь код с комментариями :
import socket
import threading
def read_sok():
while 1 :
data = sor.recv(1024)
print(data.decode('utf-8'))
server = '192.168.0.1', 5050 # Данные сервера
alias = input() # Вводим наш псевдоним
sor = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
sor.bind(('', 0)) # Задаем сокет как клиент
sor.sendto((alias+' Connect to server').encode('utf-8'), server)# Уведомляем сервер о подключении
potok = threading.Thread(target= read_sok)
potok.start()
while 1 :
mensahe = input()
sor.sendto(('['+alias+']'+mensahe).encode('utf-8'), server)
Шифрование .
У нас очень упрощенный вариант, думаю c шифрованием мудрить не будем. Возьмем самый простой симметричный алгоритм XOR. Основная идея алгоритма состоит в том, что если у нас есть некая величина, есть некий шифровальный ключ (другая величина), то можно зашифровать исходные данные через этот ключ, применив операцию XOR побитно. Т.е. если у нас есть исходная фраза a и ключ k, то x = a ^ k. Теперь, если к шифру x опять применить ключ, то получим исходную фразу, т.е. a = x ^ k .
key = 567 # Ключ шифрования crypt = '' for i in message : crypt += chr(ord(i)^key) message = crypt
Я не рассчитываю на уникальность материала, сам учусь ) Строго не судите. Подсказки и доработки приветствуются.
Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.
http.server — HTTP-серверы — документация Python 3.8.6
Исходный код: Lib / http / server.py
Этот модуль определяет классы для реализации HTTP-серверов (веб-серверов).
Предупреждение
http.server
не рекомендуется для производства. Он только реализует
базовые проверки безопасности.
Один класс, HTTPServer
, является подклассом socketserver.TCPServer
.
Он создает и прослушивает HTTP-сокет, отправляя запросы в
обработчик.Код для создания и запуска сервера выглядит так:
def run (server_class = HTTPServer, handler_class = BaseHTTPRequestHandler): server_address = ('', 8000) httpd = server_class (server_address, handler_class) httpd.serve_forever ()
- класс
http.server.
HTTPServer
( адрес_сервера , RequestHandlerClass ) Этот класс основан на классе
TCPServer
путем хранения
адрес сервера как переменные экземпляра с именемимя_сервера
и
порт_сервера
.Сервер доступен обработчику, обычно
через переменную экземплярасервера
обработчика.
- класс
http.server.
ThreadingHTTPServer
( адрес_сервера , RequestHandlerClass ) Этот класс идентичен HTTPServer, но использует потоки для обработки
запросы с помощьюThreadingMixIn
. Эта
полезен для обработки сокетов, предварительно открывающих веб-браузеры, на которых
HTTPServer
будет ждать бесконечно.
Необходимо указать HTTPServer
и ThreadingHTTPServer
RequestHandlerClass при создании экземпляра, из которого этот модуль
предоставляет три различных варианта:
- класс
http.server.
BaseHTTPRequestHandler
( запрос , client_address , сервер ) Этот класс используется для обработки HTTP-запросов, поступающих на сервер. По
сам по себе он не может отвечать ни на какие действительные HTTP-запросы; он должен быть подклассом
для обработки каждого метода запроса (например,г. GET или POST).
BaseHTTPRequestHandler
предоставляет несколько классов и экземпляров
переменные и методы для использования подклассами.Обработчик проанализирует запрос и заголовки, а затем вызовет метод
зависит от типа запроса. Имя метода состоит из
запрос. Например, для метода запросаSPAM
,do_SPAM ()
метод будет вызываться без аргументов. Все o
.
Python: давайте создадим простой HTTP-сервер (учебник)
Веб-серверы есть везде.
Черт возьми, вы прямо сейчас общаетесь с одним из них!
Независимо от того, какой вы инженер-программист, в какой-то момент вашей карьеры вам придется взаимодействовать с веб-серверами. Возможно, вы создаете API-сервер для серверной службы. Или, может быть, вы просто настраиваете веб-сервер для своего сайта.
В этой статье я расскажу, как создать самый простой веб-сервер http на Python.
Но поскольку я хочу убедиться, что вы понимаете, что мы создаем, я сначала сделаю обзор того, что такое веб-серверы и как они работают.
Если вы уже знаете, как работают веб-серверы, вы можете сразу перейти к этому разделу.
Что такое HTTP-сервер?
Веб-сервер HTTP — это не что иное, как процесс, который запущен на вашем компьютере и выполняет ровно две вещи:
1- Прослушивает входящие HTTP-запросы по определенному адресу сокета TCP (IP-адрес и номер порта, который я буду поговорим позже)
2- Обрабатывает этот запрос и отправляет ответ обратно пользователю.
Позвольте мне сделать мою мысль менее абстрактной.
Представьте, что вы открываете браузер Chrome и набираете www.yahoo.com в адресной строке.
Конечно, вы хотите, чтобы домашняя страница Yahoo отображалась в окне вашего браузера.
Но что на самом деле только что произошло под капотом?
На самом деле произошло много всего, и я мог бы посвятить целую статью объяснению того, как это произошло.
Но для простоты я отвлечусь от некоторых деталей и расскажу об этом на очень высоком уровне .
На высоком уровне, когда вы набираете www.yahoo.com в своем браузере, ваш браузер создает сетевое сообщение, называемое HTTP-запросом.
Этот запрос дойдет до компьютера Yahoo, на котором работает веб-сервер. Этот веб-сервер перехватит ваш запрос и обработает его, ответив HTML-кодом домашней страницы Yahoo.
Наконец, ваш браузер отображает этот HTML-код на экране, и это то, что вы видите на экране.
Каждое последующее взаимодействие с домашней страницей Yahoo (например, при нажатии на ссылку) инициирует новый запрос и ответ, точно так же, как и первый.
Повторюсь, машина, которая получает HTTP-запрос, имеет программный процесс, называемый веб-сервером. Этот веб-сервер отвечает за перехват этих запросов и за их обработку соответствующим образом .
Хорошо, теперь, когда вы знаете, что такое веб-сервер и какова его функция, вам может быть интересно, как вообще запрос достигает этой машины yahoo?
Хороший вопрос!
На самом деле, это один из моих любимых вопросов, которые я задаю потенциальным кандидатам на собеседовании по кодированию.
Позвольте мне объяснить как, но опять же…. На высоком уровне.
Адрес сокета TCP
Любое HTTP-сообщение (будь то запрос или ответ) должно знать, как добраться до места назначения.
Для того, чтобы добраться до места назначения, каждое HTTP-сообщение содержит адрес, называемый получателем TCP-адрес .
И каждый TCP-адрес состоит из IP-адреса и номера порта .
Я знаю все эти сокращения (TCP, IP и т. Д..) может быть ошеломляющим, если ваши сетевые концепции не сильны.
Я постараюсь сделать это простым, но если вы заинтересованы в улучшении своих знаний о сетевых концепциях, я настоятельно рекомендую эту книгу Росс и Куроз.
Так где же этот адрес, когда вы всего лишь набрали www.yahoo.com в своем браузере?
Что ж, это доменное имя преобразуется в IP-адрес через большую распределенную базу данных, называемую DNS.
Хотите узнать, что это за IP-адрес?
Легко! Подойдите к своему терминалу и сделайте следующее:
$ host yahoo.com
yahoo.com имеет адрес 98.138.219.231
yahoo.com имеет адрес 98.137.246.8
yahoo.com имеет адрес 98.138.219.232
yahoo.com имеет адрес 72.30.35.9
yahoo.com имеет адрес 98.137.246.7
yahoo.com имеет адрес 72.30.35.10
yahoo.com имеет IPv6-адрес 2001: 4998: 44: 41d :: 3
yahoo.com имеет IPv6-адрес 2001: 4998: c: 1023 :: 5
yahoo.com имеет IPv6-адрес 2001: 4998: c: 1023 :: 4
yahoo.com имеет IPv6-адрес 2001: 4998: 58: 1836 :: 10
yahoo.com имеет IPv6-адрес 2001: 4998: 58: 1836 :: 11
yahoo.com имеет IPv6-адрес 2001: 4998: 44: 41d :: 4
yahoo.com почту обрабатывает 1 mta5.am0.yahoodns.net.
Почтой yahoo.com занимается 1 mta6.am0.yahoodns.net.
Почтой yahoo.com занимается 1 mta7.am0.yahoodns.net.
Как видите, DNS переведет yahoo.com на любой из адресов выше.
Только IP-адрес позволит HTTP-сообщению прибыть на нужную машину, но вам по-прежнему нужен номер порта, чтобы HTTP-запрос прибыл точно на веб-сервер.
Другими словами, веб-сервер — это обычное сетевое приложение, которое прослушивает определенный порт.
И HTTP-запрос ДОЛЖЕН быть адресован этому порту.
Так где же номер порта при вводе www.yahoo.com ?
По умолчанию номер порта — 80 для http и 443 для https , поэтому, даже если вы явно не указали номер порта, он все еще присутствует.
И если веб-сервер прослушивает порт, отличный от номера порта по умолчанию (ни 80, ни 443), вы должны явно указать номер порта следующим образом:
К настоящему времени у вас должна быть вся необходимая информация для создания http-сети. сервер на Python.
Итак, без лишних слов, приступим.
Создать простой HTML-файл
Вот что мы хотим сделать.
Мы хотим создать простой HTTP-сервер, обслуживающий статическую веб-страницу в формате HTML.
Давайте создадим нашу html-страницу.
Python потрясающий!
После
Поздравляем! HTTP-сервер работает!
Теперь сохраните этот файл как index.html
С веб-страницей, которую мы хотим обслуживать, следующим шагом будет создание веб-сервера, который будет обслуживать эту html-страницу.
Создайте веб-сервер HTTP
Чтобы создать веб-сервер в Python 3 , вам нужно будет импортировать два модуля: http.server и socketserver
Обратите внимание, что в Python 2 был модуль с именем SimpleHTTPServer. Этот модуль был объединен с http.server в Python 3
. Давайте взглянем на код для создания http-сервера
import http.сервер
импортировать сервер сокетов
ПОРТ = 8080
Обработчик = http.server.SimpleHTTPRequestHandler
с socketserver.TCPServer (("", PORT), Handler) как httpd:
print ("обслуживание в порту", ПОРТ)
httpd.serve_forever ()
Вот так у нас есть работающий http-сервер.
Теперь давайте разберем этот код построчно.
Во-первых, как я упоминал ранее, веб-сервер — это процесс, который прослушивает входящие запросы по определенному TCP-адресу.
И, как вы уже знаете, TCP-адрес идентифицируется IP-адресом и номером порта .
Во-вторых, веб-серверу также нужно указать, как обрабатывать входящие запросы.
Эти входящие запросы обрабатываются специальными обработчиками. Вы можете представить себе веб-сервер как диспетчер, когда приходит запрос, http-сервер проверяет запрос и отправляет его назначенному обработчику.
Конечно, эти обработчики могут делать все, что вы пожелаете.
Но как вы думаете, что из себя представляет самый простой обработчик?
Ну, это будет обработчик, который просто обслуживает статический файл.
Другими словами, когда я перехожу на yahoo.com , веб-сервер на другом конце отправляет обратно статический html-файл.
На самом деле это именно то, что мы пытаемся сделать.
И это, друг мой, и есть http.server.SimpleHTTPRequestHandler: простой обработчик HTTP-запросов, который обслуживает файлы из текущего каталога и любого из его подкаталогов .
Теперь поговорим о классе socketserver.TCPServer .
Экземпляр TCPServer описывает сервер, который использует протокол TCP для отправки и получения сообщений (http — это протокол прикладного уровня поверх TCP).
Чтобы создать экземпляр TCP-сервера, нам нужны две вещи:
1- TCP-адрес (IP-адрес и номер порта)
2- Обработчик
socketserver.TCPServer (("", PORT ), Handler)
Как видите, TCP-адрес передается в виде кортежа (IP-адрес, номер порта)
Передача пустой строки в качестве IP-адреса означает, что сервер будет прослушивать любой сетевой интерфейс ( все доступные IP-адреса).
И поскольку PORT хранит значение 8080, сервер будет прослушивать входящие запросы на этом порту.
Для обработчика мы передаем простой обработчик, о котором говорили ранее.
Обработчик = http.server.SimpleHTTPRequestHandler
А как насчет serve_forever?
serve_forever — это метод экземпляра TCPServer, который запускает сервер и начинает прослушивать входящие запросы и отвечать на них.
Отлично, давайте сохраним этот файл как server.py в в том же каталоге , что и index.html , потому что по умолчанию SimpleHTTPRequestHandler будет искать файл с именем index.html в текущем каталоге.
В этом каталоге запустите веб-сервер:
$ python server.py
обслуживание на порту 8080
Таким образом, теперь у вас есть HTTP-сервер, который прослушивает любой интерфейс на порту 8080, ожидая входящих HTTP-запросов.
Пришло время повеселиться!
Откройте браузер и введите в адресной строке localhost: 8080.
Отлично! Похоже, все работает нормально.
А что такое localhost ?
localhost — имя хоста, которое означает этот компьютер .Он используется для доступа к сетевым службам, работающим на хосте, через сетевой интерфейс обратной петли.
И поскольку веб-сервер прослушивает любой интерфейс , он также прослушивает интерфейс обратной связи.
Вы хотите знать, какой IP-адрес соответствует localhost?
Вы поняли.
$ хост localhost
localhost имеет адрес 127.0.0.1
localhost имеет IPv6-адрес :: 1
Хост localhost не найден: 3 (NXDOMAIN)
Фактически вы можете полностью заменить localhost на 127.0.0.1 в вашем браузере, и вы все равно получите тот же результат.
Попробовать 🙂
One Final Word
На самом деле вы можете запустить веб-сервер с помощью Python, даже не создавая никаких сценариев.
Просто зайдите в свой терминал и выполните следующие действия (но убедитесь, что вы используете python 3)
python -m http.server 8080
По умолчанию этот сервер будет прослушивать все интерфейсы и порт 8080.
Если вы хотите прослушивать определенный интерфейс, выполните следующие действия:
python -m http.server 8080 --bind 127.0.0.1
Также, начиная с Python 3.7, вы можете использовать флаг –directory для обслуживания файлов из каталога, который не обязательно является текущим каталогом.
Итак, теперь возникает вопрос: зачем вам вообще нужно писать сценарий, если вы можете просто вызвать сервер с терминала?
Хорошо, помните, что вы используете SimpleHTTPRequestHandler . Если вы хотите создать свои собственные обработчики (что вы, вероятно, захотите сделать), вы не сможете сделать это из терминала.
Изучение Python?
Загляните в раздел «Курсы»!
Избранные сообщения
Вы начинаете карьеру программиста?
Я предлагаю свои лучшие материалы для новичков в информационном бюллетене.
- Советы по Python для начинающих, среднего и продвинутого уровней.
- CS Советы и рекомендации по карьере.
- Специальные скидки на мои премиальные курсы при их запуске.
И многое другое…
Подпишитесь сейчас. Это бесплатно.
.
21,22. http.server — HTTP-серверы — документация Python 3.4.10
Исходный код: Lib / http / server.py
Этот модуль определяет классы для реализации HTTP-серверов (веб-серверов).
Один класс, HTTPServer, является подклассом socketserver.TCPServer.
Он создает и прослушивает HTTP-сокет, отправляя запросы в
обработчик. Код для создания и запуска сервера выглядит так:
def run (server_class = HTTPServer, handler_class = BaseHTTPRequestHandler): server_address = ('', 8000) httpd = server_class (server_address, handler_class) httpd.serve_forever ()
- класс http.server.HTTPServer ( адрес_сервера , RequestHandlerClass )
Этот класс основан на классе TCPServer, сохраняя
адрес сервера как переменные экземпляра с именем server_name и
порт сервера. Сервер доступен обработчику, обычно
через переменную экземпляра сервера обработчика.
HTTPServer должен получить RequestHandlerClass при создании экземпляра,
из которых этот модуль предоставляет три различных варианта:
- класс http.server.BaseHTTPRequestHandler ( запрос , client_address , сервер )
Этот класс используется для обработки HTTP-запросов, поступающих на сервер. По
сам по себе он не может отвечать ни на какие действительные HTTP-запросы; он должен быть подклассом
для обработки каждого метода запроса (например, GET или POST).
BaseHTTPRequestHandler предоставляет несколько классов и экземпляров
переменные и методы для использования подклассами.Обработчик проанализирует запрос и заголовки, а затем вызовет метод
зависит от типа запроса.Имя метода состоит из
запрос. Например, для метода запроса SPAM функция do_SPAM ()
метод будет вызываться без аргументов. Вся необходимая информация
хранятся в переменных экземпляра обработчика. Подклассы не должны
переопределить или расширить метод __init __ ().BaseHTTPRequestHandler имеет следующие переменные экземпляра:
- client_address
Содержит кортеж в форме (хост, порт), относящийся к клиентскому
адрес.
- сервер
Содержит экземпляр сервера.
- close_connection
Логическое значение, которое должно быть установлено перед возвратом handle_one_request (),
указывает, можно ли ожидать другого запроса, или должно ли соединение
быть выключенным.
- линия запроса
Содержит строковое представление строки HTTP-запроса. В
завершающий CRLF удаляется. Этот атрибут должен быть установлен
handle_one_request ().Если действительная строка запроса не была обработана, она
должна быть установлена в пустую строку.
.
21,22. http.server — HTTP-серверы — документация Python 3.3.7
Исходный код: Lib / http / server.py
Этот модуль определяет классы для реализации HTTP-серверов (веб-серверов).
Один класс, HTTPServer, является подклассом socketserver.TCPServer.
Он создает и прослушивает HTTP-сокет, отправляя запросы в
обработчик. Код для создания и запуска сервера выглядит так:
def run (server_class = HTTPServer, handler_class = BaseHTTPRequestHandler): server_address = ('', 8000) httpd = server_class (server_address, handler_class) httpd.serve_forever ()
- класс http.server.HTTPServer ( адрес_сервера , RequestHandlerClass )
Этот класс основан на классе TCPServer, сохраняя
адрес сервера как переменные экземпляра с именем server_name и
порт сервера. Сервер доступен обработчику, обычно
через переменную экземпляра сервера обработчика.
HTTPServer должен получить RequestHandlerClass при создании экземпляра,
из которых этот модуль предоставляет три различных варианта:
- класс http.server.BaseHTTPRequestHandler ( запрос , client_address , сервер )
Этот класс используется для обработки HTTP-запросов, поступающих на сервер. По
сам по себе он не может отвечать ни на какие действительные HTTP-запросы; он должен быть подклассом
для обработки каждого метода запроса (например, GET или POST).
BaseHTTPRequestHandler предоставляет несколько классов и экземпляров
переменные и методы для использования подклассами.Обработчик проанализирует запрос и заголовки, а затем вызовет метод
зависит от типа запроса.Имя метода состоит из
запрос. Например, для метода запроса SPAM функция do_SPAM ()
метод будет вызываться без аргументов. Вся необходимая информация
хранятся в переменных экземпляра обработчика. Подклассы не должны
переопределить или расширить метод __init __ ().BaseHTTPRequestHandler имеет следующие переменные экземпляра:
- client_address
Содержит кортеж в форме (хост, порт), относящийся к клиентскому
адрес.
- сервер
Содержит экземпляр сервера.
- команда
Содержит команду (тип запроса). Например, «ПОЛУЧИТЬ».
- путь
Содержит путь запроса.
- request_version
Содержит строку версии из запроса. Например, HTTP / 1.0.
- заголовки
Содержит экземпляр класса, указанного в классе MessageClass.
переменная.Этот экземпляр анализирует и управляет заголовками в HTTP
запрос.
- rfile
.