Python: реализуем веб-сервер своими руками
Intro
Пакет 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
Теперь необходимо создать объект класса HTTPServer, передав ему в качестве параметров IP-адрес и порт, на котором будет работать http-сервер, а также наш класс-потомок BaseHTTPRequestHandler, который собственно и будет отвечать за обработку запросов к серверу:
serv = HTTPServer((«localhost»,80),HttpProcessor)
serv.serve_forever()
Первой строкой мы создаем экземпляр HTTPServer, указывая ему, по какому адресу и с помощью чего обрабатывать наши http-запросы, затем, вызовом метода serve_forever(), мы указываем интерпретатору перманентно запустить веб-сервер — приложение будет работать до тех пор, пока его работа не будет прервана путем нашего вмешательства. Убедиться в его работоспособности можно, обратившись через браузер по тому адресу, который мы забиндили при написании скрипта: нашему взору предстанет веб-страница с единственной фразой — «hello».
Как это работает
Затем необходимо отправить http-заголовки, для чего используется метод send_header(), а также эскейп-посдедовательность (\r\n\r\n), которая свидетельствует о завершении заголовка ответа:
Свой асинхронный tcp-сервер за 15 минут с подробным разбором
Ранее я представил пару небольших постов о потенциальной роли Spring Boot 2 в реактивном программировании. После этого я получил ряд вопросов о том, как работают асинхронные операции в программировании в целом. Сегодня я хочу разобрать, что такое Non-blocking I/O и как применить это знание для создания небольшого tcp–сервера на python, который сможет обрабатывать множество открытых и тяжелых (долгих) соединений в один поток. Знание python не требуется: все будет предельно просто со множеством комментариев. Приглашаю всех желающих!
Мне, как и многим другим разработчикам, очень нравятся эксперименты, поэтому вся последующая статья будет состоять как раз из серии экспериментов и выводов, которые они несут. Предполагается, что вы недостаточно хорошо знакомы с тематикой, и будете охотно экспериментировать со мной. Исходники примеров можно найти на github.
Начнем с написания очень простого tcp–сервера. Задача сервера будет заключаться в получении и печати данных из сокета и возвращения строки Hello from server!. Примерно так это выглядит:
Здесь все довольно просто. Если вы не знакомы с понятием сокета, то вот очень простая и практическая статья. Мы создаем сокет, ловим входящие соединения и обрабатываем их согласно заданной логике. Здесь стоит обратить внимание на сообщения. При создании нового соединения с клиентом мы пишем об этом в консоль.
Хочу сразу заметить, что не стоит серьезно вникать в листинги программ до полного прочтения статьи. Совершенно нормально, если что-то будет не совсем понятно в самом начале. Просто продолжайте чтение.
Нет особого смысла в сервере без клиента. Поэтому на следующем этапе необходимо написать небольшой клиент для использования сервера:
Важной особенностью здесь является тот факт, что мы в начале устанавливаем максимально возможное количество соединений, а только потом используем их для передачи/хранения данных.
Давайте запустим сервер. Первое, что мы видим:
Это означает, что мы успешно запустили наш сервер и он готов принимать входящие запросы. Запустим клиент и сразу увидим в консоли сервера (у вас порты могут быть другими):
Что и следовало ожидать. В бесконечном цикле мы получаем новое соединение и сразу же обрабатываем данные из него. В чем тут проблема? Ранее мы использовали опцию server_socket.listen(10) для настройки сервера. Она означает максимальный размер очереди из еще не принятых подключений. Но в этом нет совершенно никакого смысла, ведь мы принимаем по одному соединению. Что предпринять в такой ситуации? На самом деле, существует несколько выходов.
Как можно понять по выводу, мы принимаем новые коннекты и данные почти параллельно. Более того, мы не ждем данных от нового подключения. Вместо этого мы устанавливаем новое.
Как это работает?
Дело в том, что все наши операции с ресурсами (а обращение к сокету относится к этой категории) происходят через системные вызовы операционной системы. Если говорить коротко, то системные вызовы представляют собой обращение к API операционной системы.
Рассмотрим, что происходит в первом случае и во втором.
Синхронный вызов
Давайте разберем рисунок:
Первая стрелка показывает, что наше приложение обращается к операционной системе для получения данных из ресурса. Далее наша программа блокируется до появления нужного события. Минус очевиден: если у нас один поток, то другие пользователи должны ждать обработку текущего.
Асинхронный вызов
Теперь посмотрим на рисунок, который иллюстрирует асинхронный вызов:
Первая стрелка, как и в первом случае, делает запрос к ОС (операционной системе) на получение данных из ресурсов. Но посмотрите, что происходит далее. Мы не ждем данных из ресурса и продолжаем работу. Что же делать нам? Мы отдали распоряжение ОС и не ждем результат сразу. Простейшим ответом будет самостоятельно опрашивать нашу систему на наличие данных. Таким образом, мы сможем утилизировать ресурсы и не блокировать наш поток.
Но самом деле такая система не является практичной. Такое состояние, в котором мы постоянно смотрим на данные и ждем какого-то события, называется активным ожиданием. Минус очевиден: мы впустую тратим процессорное время в случае, когда информация не обновилась. Более правильным решением было бы оставить блокировку, но сделать ее «умной». Наш поток не просто ждет какого-то определенного события. Вместо этого он ожидает любое изменение данных в нашей программе. Именно так и работает функция select в нашем асинхронном сервере:
Теперь можно вернуться к реализации нашего асинхронного сервера и взглянуть на него с новым знанием. Первое, что бросается в глаза, – метод работы. Если в первом случае наша программа выполнялась “сверху вниз”, то во втором случае мы оперируем событиями. Такой подход в разработке ПО называется событийно-ориентированным (event-driven development).
Сразу стоит отметить, что такой подход не является серебряной пулей. У него имеется масса недостатков. Во-первых, такой код сложнее поддерживать и менять. Во-вторых, у нас есть и всегда будут блокирующие вызовы, которые все портят. Например, в программе выше мы воспользовались функцией print. Дело в том, что такая функция тоже обращается к ОС, следовательно, наш поток выполнения блокируется и другие источники данных терпеливо ждут.
Заключение
Спасибо, что прочитали статью до конца! В следующий раз (уже скоро) я расскажу о прочих возможных неблокирующих ситуациях в жизни наших программ. Буду рад вас видеть и в следующих постах.
HTTP серверы на Python
Введение
Примеры
Запуск простого HTTP-сервера
Если вы хотите работать только на локальном хосте, вам нужно написать собственную программу на Python, такую как:
Обслуживание файлов
Предполагая, что у вас есть следующий каталог файлов:
Вы можете настроить веб-сервер для обслуживания этих файлов следующим образом:
import SimpleHTTPServer import SocketServer PORT = 8000 handler = SimpleHTTPServer.SimpleHTTPRequestHandler httpd = SocketServer.TCPServer ((«localhost», PORT), обработчик) print «Обслуживание файлов в порту <>«. Формат (PORT) httpd.serve_forever () import http.server import socketserver PORT = 8000 handler = http.server.SimpleHTTPRequestHandler httpd = socketserver.TCPServer ((«», PORT), обработчик) print («обслуживание в порту», PORT) httpd.serve_forever ()
SocketServer модуль предоставляет классы и функциональные возможности для настройки сетевого сервера.
SimpleHTTPRequestHandler класс SimpleHTTPServer модуля позволяет файлы в текущем каталоге будут обслужены.
Сохраните скрипт в том же каталоге и запустите его.
Открыть локальный: 8000 в браузере, это даст вам следующее:
Программный API SimpleHTTPServer
Тестовая функция вызывается после обработчиков запросов и ServerClass. Теперь BaseHTTPServer.test вызывается
Следовательно, здесь номер порта, который пользователь передал в качестве аргумента, анализируется и привязывается к адресу хоста. Далее выполняются основные этапы программирования сокетов с заданным портом и протоколом. Наконец сервер сокета инициируется.
Это базовый обзор наследования от класса SocketServer другим классам:
Ссылки https://hg.python.org/cpython/file/2.7/Lib/BaseHTTPServer.py и https://hg.python.org/cpython/file/2.7/Lib/SocketServer.py полезны для нахождения дальнейшего Информация.
Базовая обработка GET, POST, PUT с использованием BaseHTTPRequestHandler
Пример выходных данных с помощью curl :
Синтаксис
Параметры
Примечания
Научим основам Python и Data Science на практике
Это не обычный теоритический курс, а онлайн-тренажер, с практикой на примерах рабочих задач, в котором вы можете учиться в любое удобное время 24/7. Вы получите реальный опыт, разрабатывая качественный код и анализируя реальные данные.
Аналоги switch в Python
Работа с базами данных
Введение Примеры Доступ к базе данных MySQL с использованием MySQLdb Первое, что вам нужно сделать, это создать соединение с базой данных, используя метод connect. После этого вам понадобится курсор, который будет работать с этим соединением. Используйте
Создание клиент-сервер на Python для зародышей
Приветик. В данной статье не будет никакой занудной теории по типу «А чТо ТаКоЕ СЕРВЕР. «. Мы будем вкратце описывать работу клиент-сервер, а также приведём примеры. Данная статья будет интересна тем, кто не до конца понимает как работает клиент-сервер, кто забивает в череп гвозди, чтобы повысить содержание железа и стать умнее, кто думает что клиент-сервер это что-то заоблачное для обычного начинающего кодера.
Итак, приступим к описанию принципов работы:
Сервер, на IP адресе по порту ожидает подключения на указанный IP адрес с портом . IP машины и IP приёма должны быть одинаковы. Порты имеют значения в диапазоне 0-65535.
Сервер видит что кто-то хочет посмотреть нюдсы Иванова Ивана и просто отсылает их обратно клиенту.
— Вот так мы максимально кратко расписали клиент-сервер. Распишем немного побольше.
— ЭЙ СТОЙ! А чо ещё за TCP и UPD?
— Секундочку.
Что такое UPD и TCP?
Вкратце это технологии передачи данных между двумя устройствами. Они оба разные как лолихантер и милфхантер. Приведём парочку примеров:
— Эй, Санёк, я тут камни нашёл. Можно я в тебя его кину?
— Хорошо, Шанёк, кидай
— Разрешение кидать получено!
*Кинул камни настолько мягко и последовательно, что Санёк успел словить все*
Это был пример работы TCP. Он превосходит UPD в целостности данных, строго последовательным отправлением данных и большей надёжности, но в отличии от него меньшей скоростью.
— Эй, Санёк, лови!
*Кинул камни так сильно, что Санёк сразу дал дёру, успев сначала словить большую часть камней*
— *****, не поймал, в лицо попал
Это был пример работы UPD. В отличии от своего «прилежного» брата он более быстрый в закидывании камня. Но вместо строгой последовательности отправки данных, кидает всё что видит.
Теперь черпанём немного практики.
Для начала сделаем вечно получающий информацию сервер.
Для передачи информации через сокеты в Python используем socket
Теперь же нам надо сделать слушалку.
Теперь у нас есть почтовый ящик и заранее готовая коробка с посылкой. Теперь нам нужно проверять наш почтовый ящик. Наш почтовый ящик вмещает 1 КБ (1024 байт). Поэтому нам нужно каждый раз открывать ящик, забирать оттуда данные и продолжать до тех пор, пока ящик не опустеет.
Вот так мы будем получать информацию о том, что нам отправили. Это как будто вы перевернули почтовый ящик вверх дном и высыпаете оттуда всё до конца.
— Но зачем в начале нам отправлять данные?
Таким образом мы показываем клиенту что готовы работать. Если бы мы этого не сделали, клиент бы стоял молчал.
Раз уж с сервером окончено перейдём к клиенту
Ну вот, теперь при запуске клиента по IP, от сервера мы получим «Привет», а сервер получит наше «И тебе привет!», а также продолжит ждать от нас ответ.
Прошу акцентировать внимание, что эти каналы легко прослушать, а по сему для передачи личных данных желательно пользоваться алгоритмами шифрования (например идеально подойдёт RSA).
Немного дополнительных вопросов:
Можно ли при помощи сокетов сделать консоль? Как реализовать в нём команды?
Конечно. Команды можно реализовать просто отправкой сообщения, а после получения обработкой через условия.
Как сделать таймаут?
Что если я укажу чужой адрес в прослушивании сервера?
Вам просто выбьет ошибку.
Можно ли по сокетам передавать фото, видео и т.д.?
Да, конечно. Фото и видео это просто данные. Их можно прочесть, а соответственно и отправить.
Небольшая справочка по основным командам для создания сокетов:
Дополнительные источники, полезные ссылки:
И на этом мы закончим вступление клиент-сервер в Python для зародышей. Напомню что данная статья не создана для Truehard кодеров, она создана для тех кто не разбирается в сокетах и хочет понять как с ними работать.
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 pi@192.168.1.102 (для Windows) или ssh pi@192.168.1.102 для Linux или OSX.
Однако, IP-адреса могут меняться, например после перезагрузки маршрутизатора, это не всегда удобно. Из этого следует Совет N2 — настроить статический IP-адрес. Для этого на Raspberry Pi выполняем команду sudo nano /etc/dhcpcd.conf, и вводим следующие настройки:
Если нужен адрес 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, и вводим команду:
Все, на Raspberry Pi работает файловый сервер! Достаточно зайти на страницу http://192.168.1.102:5000 и мы увидим наши файлы в браузере:
Способ 2: SimpleHTTPServer
Мы можем довольно просто интегрировать такой же сервер в нашу программу на Python, для этого достаточно запустить его отдельным потоком при старте программы. Теперь, нам не надо возиться с командной строкой, пока программа запущена, сервер будет работать.
Команда os.chdir является опциональной, если мы хотим предоставить доступ из сервера к какой-то другой папке, кроме текущей.
Способ 3: HTTPServer
Это уже полноценный web-сервер, способный обрабатывать GET и POST-запросы, возвращать разные данные и пр. Но и кода разумеется, понадобится больше.
Рассмотрим минимально работающий вариант сервера:
Запускаем браузер, и видим в нем нашу HTML-страницу:
Данный сервер несложно научить отдавать файлы, например изображения.
Добавим в HTML тег img:
Исходный файл «raspberrypi.jpg» разумеется, должен лежать в папке с программой. Добавим в функцию do_GET возможность получения файлов:
Запускаем сервер, и видим соответствующую картинку:
Вряд ли такой сервер выиграет конкурс веб-дизайна, но он вполне работает. Сервер несложно заставить отдавать более полезные данные, например возвращать информацию о работе программы. Для примера добавим обработчик для новой функции status:
Теперь мы можем открыть в браузере ссылку http://192.168.1.102:5000/status и увидеть текущие параметры системы:
Кстати, как можно видеть, мы отдаем данные в формате JSON, что позволит использовать их для каких-то других запросов.
Заключение
Все задуманное в одну часть не влезло. Вторая часть доступна по ссылке.
Важно: меры безопасности
Если для Raspberry Pi будет использоваться внешний IP-адрес, обязательно стоит помнить о мерах безопасности. Может показаться что ваш мини-сервер никому не нужен, однако сейчас не составляет труда пакетно просканировать все диапазоны IP-адресов (как пример, Украина, Австрия) и найти все доступные устройства. Так что обязательно стоит поменять пароль на Raspberry Pi, и не стоит хранить на устройстве какую-либо конфиденциальную информацию (папки Dropbox, имена/пароли захардкоженные в скриптах, фото и пр).