• Уважаемые посетители сайта esp8266.ru!
    Мы отказались от размещения рекламы на страницах форума для большего комфорта пользователей.
    Вы можете оказать посильную поддержку администрации форума. Данные средства пойдут на оплату услуг облачных провайдеров для сайта esp8266.ru
  • Система автоматизации с открытым исходным кодом на базе esp8266/esp32 микроконтроллеров и приложения IoT Manager. Наша группа в Telegram

Micropython HTTP сервер

evgeny2k

New member
Всем привет. Хочу поделиться с сообществом своей наработкой, а именно модулем HTTPServer. Сервер работает в блокирующем режиме, но для себя использовал и неблокирующую версию. При написании сервера использовал вот эту статью:
https://andreymal.org/socket3
Рекомендую почитать для полного понимания.
Во вложении файл сервера и пример использования. Также дублирую пример здесь. Сервер работает только с get-запросами, но принимает и параметры. Предложения по доработке и добрая критика приветствуются.
Код:
# -*- coding: utf-8 -*-

from HTTPServer import Server
from machine import Pin
import gc

led = Pin(2, Pin.OUT) # Присваиваем переменной led GPIO2 и назначаем его выходом
led.high() # Переводим порт в состояние 1

def test1(path): # Пример функции, принимающей только параметр path..
    print('this is func test1 with path='+path)
    return 'this is func test1 with path='+path

def test2(path,params): # Эта фуекция принимает на вход path и params, где params имеет вид словаря вида {'param_name':'param_value'}
    print('this is func test2 with path='+path+' and params='+str(params))
    return 'this is func test2 with path='+path+' and params='+str(params)

def stop(): # Пример ф-ии без входных параметров. Остановка сервера.
    test.stop = True
    return 'stoped'

def free(): # Пример ф-ии без параметров. Показывает в браузере состояние памяти.
    result = "mem free:"+str(gc.mem_free())+" mem allocated:"+str(gc.mem_alloc())
    return result

def switch(): # Ф-ия без параметров. Просто переключает состояние светодиода на ESP
    led.value(not led.value())
    return 'Pin 2 status '+str(led.value())

test = Server(8266) # Создаём объект сервера с указанием номера порта. По умолчанию используется порт 8080
test.RouteAdd('/test1', test1) # Добавляем обрабатывающую ф-ию test1 для маршрута /test1
test.RouteAdd('/test2', test2) # Так поступаем и для остальных маршрутов и их функций
test.RouteAdd('/stop', stop)
test.RouteAdd('/free', free)
test.RouteAdd('/switch', switch)
test.Run() # Запускаем сервер

upd:
Что-то архив не могу залить. Даю листинг здесь.
Код:
# -*- coding: utf-8 -*-
import socket

class Server:
    def send_answer(self, conn, status="200 OK", typ="text/plain; charset=utf-8", data=""):
        data = data.encode("utf-8")
        conn.send(b"HTTP/1.1 " + status.encode("utf-8") + b"\r\n")
        conn.send(b"Server: simplehttp\r\n")
        conn.send(b"Connection: close\r\n")
        conn.send(b"Content-Type: " + typ.encode("utf-8") + b"\r\n")
        conn.send(b"Content-Length: " + bytes(len(data)) + b"\r\n")
        conn.send(b"\r\n")# после пустой строки в HTTP начинаются данные
        conn.send(data)

    def RouteAdd(self, path, funcname):
        if path not in self.routes:
            self.routes[path] = funcname

    def parse(self, conn, addr):# обработка соединения в отдельной функции
        data = b""
        while not b"\r\n" in data: # ждём первую строку
            tmp = conn.recv(1024)
            if not tmp: # сокет закрыли, пустой объект
                break
            else:
                data += tmp

            if not data: # данные не пришли
                return # не обрабатываем

            udata = data.decode("utf-8")
            # берём только первую строку
            udata = udata.split("\r\n", 1)[0]
            # разбиваем по пробелам нашу строку
            method, string, protocol = udata.split(" ", 2)
            if string.find('?') != -1:
                address = string.split('?')[0]
                params = dict(b.split('=') for b in string.split('?')[1].split('&'))
            else:
                address = string
                params = {}
            if method != "GET":
                self.send_answer(conn, "404 Not Found", data="Page not found")
                return
            if address in self.routes:
                if len(params) > 0:
                    self.send_answer(conn, typ="text/html; charset=utf-8", data=self.routes[address](address, params))
                else:
                    try:
                        self.send_answer(conn, typ="text/html; charset=utf-8", data=self.routes[address]())
                    except:
                        self.send_answer(conn, typ="text/html; charset=utf-8", data=self.routes[address](address))
                return

            else:
                self.send_answer(conn, "404 Not Found", data="Page not found")
                return

    def __init__(self,port=8080):
        self.routes = {}
        self.stop = False
        self.sock = socket.socket()
        self.sock.bind( ("", port) )
        self.sock.settimeout(2)
        self.sock.listen(5)

    def Run(self):
        try:
            while 1: # работаем постоянно
                try:
                    if self.stop: break
                    conn, addr = self.sock.accept()
                    #print("New connection from " + addr[0])
                except:
                    continue
                try:
                    self.parse(conn, addr)
                except:
                    self.send_answer(conn, "500 Internal Server Error", data="Error")
                finally:
                # так при любой ошибке
                # сокет закроем корректно
                    conn.close()
        finally:
            self.sock.close()
            # так при возникновении любой ошибки сокет
            # всегда закроется корректно и будет всё хорошо
 
Последнее редактирование:

straga

New member
А сколько RAM памяти он потребляет на ESP?

А можно не блокирующую версию посмотреть тоже.
 
Последнее редактирование:

corpse

New member
Добрый день! А как быть с передачей параметров? Не могу разобраться. :(
test.RouteAdd('/test2', test2) - здесь мы передаём в качестве второго параметра метод. Но если мне нужно будет в этом методе добавить обработку хэдеров, урла?
 

Cosmatos

New member
при попытке импорта выдает ошибку. Говорит не знает такого модуля.
ImportError: no module named 'HTTPServer'
 

__ab__

New member
если использовать select, можно написать очень простой вариант сервера, который не будет особо мешать выполнению чего-то еще:
Код:
import socket,select

def handle_http(client, client_addr):
    client.send("HTTP/1.0 200 OK\r\n\r\nHelloWorld!!!\r\n  %s" % str(client_addr))
    client.close()

def serv(port=80):
    http = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    addr = (socket.getaddrinfo("0.0.0.0", port))[0][-1]
    http.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    http.bind(addr)
    http.listen(4)

    while True:
        r, w, err = select.select((http,), (), (), 1)
        if r:
            for readable in r:
                client, client_addr = http.accept()
                handle_http(client, client_addr)
    # a cюда можно вставить обработку еще-чего-то
    # а можно вставить такую обработку по таймеру

serv()
 
Последнее редактирование:

lex.golubtsov

New member
если использовать select, можно написать очень простой вариант сервера, который не будет особо мешать выполнению чего-то еще:
Код:
import socket,select

def handle_http(client, client_addr):
    client.send("HTTP/1.0 200 OK\r\n\r\nHelloWorld!!!\r\n  %s" % str(client_addr))
    client.close()

..........

serv()
Привет спаситель!
Специально зарегистрировался чтобы сказать СПАСИБО за пример!
Питоном и сокетами раньше не пользовался, примеры в интернете сильно навороченные, нихрена не понятно, эта страница попадалась, но там хрень с блокировкой из примеров в низ не сразу прокрутил. Ну вот как-то так.

PS. Если выкладываешь куда-нибудь свои скрипты для микроконтроллеров, напиши пожалуйста сюда
 

__ab__

New member
Привет!
Рад что помог )
Выкладываю, обычно, в рабочие репозитории - на заказ..
В основном для компьютеров, а не для контроллеров )
 

BigStupidBeast

New member
Привет!
Рад что помог )
Выкладываю, обычно, в рабочие репозитории - на заказ..
В основном для компьютеров, а не для контроллеров )
Тоже зарегался плюсануть и вопрос задать.
А где прочитать/посмотреть про сокеты серверы? для чайников. Пока всё что попадалось как-то напоминает тот мем про сову. Можно на буржуинском
 

__ab__

New member
Сокетам уже очень много лет..
Поэтому документации по ним - море.
Можно просто читать доку по Python3 потом пробовать в Micropython
Поэтому главный сайт: https://www.python.org/
Тут легко найти доки на микропитон для esp: http://docs.micropython.org/en/latest/
Тут доки по сокетам: https://docs.python.org/3/library/socket.html
Всё вобщем-то легко ищется...
 

oxidizer

New member
Добрый день.
Опробовал этот код.
И вот в чём проблема:
Если текст HTML страницы не велик - то всё в порядке.
Но если к примеру более 1500 символов - то в браузере firefox мелькает страница на долю секунды а затем появляется сообщение:
Соединение было сброшено
Во время загрузки страницы соединение с сервером было сброшено.

А chrom в первый раз открывает пустую страницу а после обновления отображает нормально

IE говрит:
Не удается открыть эту страницу

Вот код который работает:
Код:
import socket,select

html = """
<!DOCTYPE html>
<html>
<head>
<title>ESP Web Server</title>
</head>
<body>
<h1>ESP Web Server</h1>
<p>text text text text text text text text text text text text text text</p>
<p>text text text text text text text text text text text text text text</p>
<p>text text text text text text text text text text text text text text</p>
</body>
</html>
"""

def handle_http(client, client_addr):
    client.send('HTTP/1.0 200 OK\r\ncontent-type: text/html; charset=UTF-8\r\n\r\n' + html)
    client.close()

def serv(port=80):
    http = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    addr = (socket.getaddrinfo("0.0.0.0", port))[0][-1]
    http.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    http.bind(addr)
    http.listen(4)

    while True:
        r, w, err = select.select((http,), (), (), 1)
        if r:
            for readable in r:
                client, client_addr = http.accept()
                handle_http(client, client_addr)
serv()
Но если увеличить
<p>text text text text text text text text text text text text text text</p>
<p>text text text text text text text text text text text text text text</p>
<p>text text text text text text text text text text text text text text</p>

в два раза:
<p>text text text text text text text text text text text text text text</p>
<p>text text text text text text text text text text text text text text</p>
<p>text text text text text text text text text text text text text text</p>
<p>text text text text text text text text text text text text text text</p>
<p>text text text text text text text text text text text text text text</p>
<p>text text text text text text text text text text text text text text</p>

Происходит вышеописанная проблема.
Может кто нибудь подскажет в чём дело?
Можте с заголовком что то не так?
Прошивки пробовал разные.
Сейчас прошивка: esp8266-20191220-v1.12.bin (MicroPython)
 

__ab__

New member
Я бы не рекомендовал передавать с железки больше, чем по килобайт.
def handle_http(client, client_addr):
data = b"""<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>Test Web Server</h1>
<p>text text text text text text text text text text text text text text</p>
<p>text text text text text text text text text text text text text text</p>
<p>text text text text text text text text text text text text text text</p>
<p>text text text text text text text text text text text text text text</p>
<p>text text text text text text text text text text text text text text</p>
<p>text text text text text text text text text text text text text text</p>
<p>text text text text text text text text text text text text text text</p>
<p>text text text text text text text text text text text text text text</p>
<p>text text text text text text text text text text text text text text</p>
<p>text text text text text text text text text text text text text text</p>
<hr>
Is OK!
</body>
</html>"""
data = b'HTTP/1.0 200 OK\r\ncontent-Length: %s\r\ncontent-type: text/html; charset=UTF-8\r\n\r\n%s' % (str(len(data)).encode('ascii'), data)
client.send(data)
client.close()

На 8266 вряд-ли можно сделать серьёзный веб-сервер ))

Скорее стоит с контроллера отдавать данные на сервер (например через MQTT), а уже на сервере делать вебку.
Если нагрузка 1-2 пользователя, в качестве такого сервера вполне сойдет Raspberry Pi, например.
При большей нагрузке стоит подумать о том, чтобы веб-сервер разместить на компьютере.
 

oxidizer

New member
Этот код так же не работает.
В хроме недогружает весь текст.

Screenshot_1.png

В фаерфокс вот такая беда:

Screenshot_2.png
 
Сверху Снизу