5 способов сделать Python-сервер на Raspberry Pi. Часть 1

  • Tutorial
Привет, Хабр.

Сегодня в большом числе проектов домашней (и не только) автоматизации используется 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, и вводим следующие настройки:

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><h3>Hello from the Raspberry Pi</h3><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: Для понимания картины добавил опрос

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Насколько актуальны на Хабре статьи для начинающих?

  • 11.7%Не актуальны, описанное и так всем известно23
  • 45.4%Актуальны, узнал что-то новое89
  • 42.8%Актуальны, начинаю с нуля, как раз то что нужно84
Поделиться публикацией
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 43

    +3

    А где остальные 4?


    По сути всё, что вы делаете тут — это запускаете стандартный http.server одним способом. Я уж грешным делом подумал, что тут и systemd будет, и nginx unit, и синие киты.

      +2
      Странный совет про статический IP.
      То есть, если дать ему 102 в данном случае, а потом другое устройство получит 102 по DHCP, то как получить доступ?
      Статический лучше выдавать за пределами DHCP диапазона.

      P.S.: Я обычно стараюсь таблицу статических адресов держать на самом маршрутизаторе, что бы было в одном месте, но в данном случае это уже не так важно.
        0
        Да, согласен, спасибо за уточнение.
        0
        В разных ОС по разному устанавливаются и запускаются службы, во многих компоненты можно выбрать при установке, я так и не понял по какую ОС статья.
          0
          Приведенный код является кроссплатформенным, должно работать везде.
          +6
          Не советую в конфигах устройства прописывать статический адрес.
          Гораздо правильнее будет в роутере создать статическую привязку «MAC address = IP Adress».
          По какой-либо причине изменится IP пространство на интерфейсе устройства ( подсеть ) — будет будет увлекательный квест по получению доступа к ней.
            0
            Мой копророутер таких настроек вроде не имеет, но в целом согласен, идея хорошая.
            0
            еще один туториал ни о чем?
              +1

              Круто. Спасибо большое.

                0

                Есть вопрос по стабильности. Если нужно чтобы http был доступен 24х7 хватит и такой реализации? Что будбет если устройство начнут буртфорсить?

                  0
                  Многолетней статистики у меня нет.

                  2-3 месяца домашний мини-сервер у меня как-то работал, дольше нужды не было. Электронные часы на Raspberry Pi проработали пару лет, потом сдохла SD-карта (хотя её можно было бы сделать read-only, тогда проблем бы не было).

                  Если задачи ресурсоемкие, для Raspberry Pi обязателен хороший блок питания и радиатор на проц, иначе виснет от перегрева.
                    0

                    Добавлю, что есть дополнительные модули с бесперебойником полноценным.

                  +1

                  не советую использовать Google DNS в качестве резервного, многие ресурсы подолгу остаются недоступными после изменений в зонах, особенно часто вообще не резолвятся в случае добавления субдоменов, гораздо лучше работают DNS сервера от Yandex

                    0
                    Спасибо. Ни разу их не использовал, буду иметь в виду.
                    0

                    Самое интересное — включение самоделки в автозагрузку, назначение прав, запись/незапись логов… про это ни слова.
                    Что произойдет, если внутри есть некий неатомарный ногодрыг (опрос внешнего датчика к примеру) и веб-сервер при обработке 2 почти одновременно пришедших запросов выполнит эти действия параллельно в 2 потоках? Это же raspberry, туда обычно подключают разную периферию

                      0
                      Если у аудитории будет интерес, можно дойти и до многопоточности. Но по рейтингу оценок… сомневаюсь что до этого дойдет. Со статьями про телефонных мошенников, штурм немецкого бункера или украинца Незнайку мне конкурировать без шансов :)
                        0
                        Просто уровень статьи вызывает вопросы.
                        Кому-то и этого много, кому-то этого откровенно мало (вопросы для рассмотрения iig поддержу).
                        К примеру, в статье нет примеров с WSGI, который считается более «зрелым» методом, потому что все проблемы с безопасностью, брутфорсом и тому подобным берут на себя решения на базе Apache/Nginx веб-серверов, а не решения для разработчиков на базе Питона, где вопросы с безопасностью выставления такого в Интернет вообще решено было оставить за скобками статьи.
                          +1
                          Просто уровень статьи вызывает вопросы

                          Уровень расчитан на начинающих, это прямо указано в первом же абзаце.
                          В вопросах ничего плохого нет, добавлю в wish-list.
                            0
                            Про добавление в автозагрузку стоит добавить прямо в эту статью.

                            Потому что если представить себе, что RPi купил обычный человек (имевший опыт только с Windows), то работа с Cron'ом или тем более systemd для добавления старта своей программы по target'у потребует гуглежа и многочисленных неудачных попыток.
                              0
                              Так можно и без systemd для начала обойтись, просто вписать команду в /etc/rc.local. Ничуть не сложнее, чем когда-то был autoexec.bat (если кто помнит:).
                                +1

                                Просто вписать можно, но не факт, что все заработает, так как окружение при запуске из rc.local совсем другое. А если ваш скрипт из rc.local запустится до того, как сетевые интерфейсы поднимутся? И текущая папка другая будет? И пользователя хорошо бы сменить, чтобы от рута не запускать ваш сервер. Короче, дьявол в детялах, и, кстати, по-моему, с systemd это как раз можно сделать унифицированно, без необходимости городить свои велосипеды.

                                  0
                                  Да, в systemd можно указать явно, после какого сервиса стартовать, но если не ошибаюсь, для rc.local это тоже прописано в системе. Root обычно по-любому нужен если нужен GPIO и работа с периферией (либо надо разделять сервисы на 2 разных).

                                  Есть краткий туториал по разным способам запуска тут кстати: www.dexterindustries.com/howto/run-a-program-on-your-raspberry-pi-at-startup
                                    0
                                    Root обычно по-любому нужен если нужен GPIO и работа с периферией (либо надо разделять сервисы на 2 разных).

                                    Не обязателен, обычно по-умолчанию в таких дистрибутивах выделяются специальные группы gpio,i2c,spi… И даже в том случае, если ваша программа работает напрямую с памятью, то часть ее все равно выделена в /dev/gpiomem на которую даются права пользователям из группы gpio.
                            0
                            Просто, как мне кажется, для тех, кто ещё совсем ничего на Raspberry Pi не делал, рановато читать статью, т. к. поднимать http-сервер на python это не первое, что будет нужно (да и в самой статье про это сказано: «у нас уже есть супер Python-программа, делающая что-то очень важное»), а для тех, кто эту супер Python-программу, делающую важное, уже написал — воспользоваться стандартной библиотекой http не составит труда и без статьи.
                              0
                              Научиться мигать светодиодом на Raspberry Pi можно и самостоятельно, а сетевое программирование штука куда более запутанная, и иметь под рукой рабочий пример довольно-таки удобно. Плюс для многих это хобби, наряду с Ардуиной и прочим, и не все занимаются Python профессионально.

                              Как показала статистика опроса, 85% читателей все же нашли что-то полезное, хотя я вполне понимаю что для профи такие статьи читать может быть скучно (так ведь никто и не заставляет, в первом абзаце сразу написано что текст для начинающих).
                          +2
                          Со статьями про телефонных мошенников, штурм немецкого бункера или украинца Незнайку мне конкурировать без шансов
                          Вы опрометчиво опубликовали статью в пятницу вечером. Во вторник или среду отклик был бы лучше.
                            0

                            В статье ожидается увидеть что-то интересное. Либо из теории, либо необычное know-how, либо изящное решение практической задачи. Но helloworld это слабовато.

                              0
                              Туториалы Hello world для начинающих тоже нужны.
                              Но да, как я и написал в самом начале, это был эксперимент, посмотреть по оценкам насколько такие статьи востребованы. Нет значит нет, не вопрос, буду смотреть статистику в конце 3х дней голосования.

                              Кстати скажу по секрету, что недовольные, ставящие минусы, есть везде, независимо от уровня статьи, хоть про hello world, хоть про нейронные сети, хоть про декодирование RDS.
                            0

                            Интересно в основном для рид онли участников, имхо. Но в формировании рейтинга таковые не участвуют.

                              0
                              По количеству закладок под статьей можно косвенно судить, насколько статья полезна, в том числе учитывая RC/RO участников.
                              0

                              ну вы же указываете заранее, что этот текст предназначен для новичков. Заранее озвучиваете проблемматику. Я не понимаю "умников", которые либо не читают вступление либо читают, но намеренно заходят в комментарии вставить своё "веское фи!".
                              Славабогу, есть более грамотные/опытные специалисты, которые просто добавляют или корректируют описанное. От этого статья становится ещё полезней.
                              Пожалуйста, пишите ещё. И про Пайтон, и про Распбери.

                            0
                            Очень содержательная статья. Нет конечно. Название нужно поменять на «Как запустить python на linux».
                              +3

                              Для быстрого и энергоэффективного HTTP-сервера лучше взять uWSGI — он может без проблем смотреть "в мир" и обладает огромной гибкостью конфигурации, а в случае необходимости можно подключить один из WSGI-совместимых фреймворков. Его также можно подружить с aiohttp поверх uvloop — получите удобный и высококонкуррентный сервер на современных async/await, а uWSGI будет просто выступать мастер-процессом, контролирующим рабочие процессы.

                                0
                                sudo ifdown wlan0 & sudo ifup wlan0 — поднять сеть без перезагрузки.

                                А так, интересно было бы еще узнать, какую нагрузку (сколько клиентов) выдержат сервера.
                                  +1
                                  Я начинающий, мне статья понравилась. Я хотел бы прочитать следующую статью из серии.

                                  Много вопросов/комментариев на тему: «не написано вот об этом». Мне как новичку, всегда хочется увидеть минимально работающий код, чтобы понять что именно делает каждая строчка в коде. В полноценной программе сложнее понять, что отвечает за основной функционал, а что отвечает за обработку ошибок, логгирование и т.д.
                                    0
                                    Спасибо за поддержку. Именно такая цель и ставилась, обойтись по возможности минимумом кода без лишних функций.
                                    0
                                    Прошу прощения за нубский вопрос, я правильно понял, что созданный сервер будет виден только из локальной сети? Мне было бы интересно узнать как сделать его видимым из интернета, с учётом динамического IP.
                                      0
                                      Да, верно. Ищите в гугле по запросу «проброс портов», как вывести сервер наружу.
                                        +2

                                        +DDNS не забудьте.

                                          0
                                          Не поможет. Большая часть провайдеров сейчас использует NAT и давно уже не выделяет белые ip обычным пользователям. Да и поделка не вынесет жизни в инете. Или сломают или заддосят. Хватит одного скрипта, который в несколько потоков начнет подбирать пароль, которого здесь и нет.
                                          По моему мнению, если это планируется выставлять в инет, то нужно:
                                          1. Снять за пару баксов в месяц VPS. И разместить на нем web интерфейс, сервер MQTT и VPN-сервер.
                                          2. Клиентскую часть на raspberry pi реализовать в виде клиента mqtt. vpn поднимать при подъеме сети.
                                          Если устройство одно в своем роде, то mqtt-сервер можно оставить на стороне raspberry pi, тогда можно будет написать приложение для android для руления по локальной сети без интернета.
                                          Возможен пункт один без vpn, но тогда придется еще и с шифрованием mqtt заморочиться. И vpn удобнее в плане доступа к малине для диагностики.
                                        0
                                        Это достаточно удобно, если нужно открыть удаленный доступ к каким-либо файлам с минимумом затраченных сил.

                                        Достаточно удобно — это подключиться по scp. А входить по ssh, затем поднимать сервер, затем заходить на сервер, а после убивать процесс — это не достаточно удобно.
                                        С другой стороны, это ведь всего лишь пример использования, надеюсь)
                                          0
                                          Когда как. WinSCP вполне удобен, а UI-клиента под Linux я так и не нашел (хотя не особо долго искал).

                                          Консольный scp у меня настроен на hotkey в PyCharm, чтобы файлы проектов было удобно на Raspberry Pi копировать.

                                        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                        Самое читаемое