• Система автоматизации с открытым исходным кодом на базе esp8266/esp32 микроконтроллеров и приложения IoT Manager. Наша группа в Telegram

Обсуждение Парсинг URL &key=val&...

Nail

New member
Здравствуйте уважаемые форумчане.
Есть такая потребность в http страничке на самой esp8266, к примеру для настройки wifi и т.д.

структура для хранения данных
Код:
typedef struct {
    char id[32];
    char ssid[32];
    char pwd[64];
    ....
} config_t

Вот функция для парсинга .
Код:
int ParseURLtoStr(char const *in, char *out, const char *searchstr, char delim, int maxLength)
{
    char *data = strstr( in, searchstr );
    if(!data) return 0;
    data += strlen(searchstr);
    int i = 0;
    while( (data[i] != delim) && (i < maxLength ))i++;
    strncpy( out, data, i );
    printf("%s => %s\n", searchstr, out);
    return i;
}
И далее гдето в программе в обработчике приема сообщений пишу :

Код:
static config_t config;
void ICACHE_FLASH_ATTR connectionHandler()
{
    while(isConnected())
    {
        int len;
        char *data = new char[128];
        len  = read(data, 128);
        ParseURLtoStr(data, config.id,    "__devid=" , '&', 16 );
        ParseURLtoStr(data, config.ssid, "__ssid=",  '&', 16 );
        delete[] data;
    }
}
И в итоге при отправке формы некоторые поля структуры заполняются, некоторые заполняются мусором. Пока немогу понять почему так происходит, если кто нибудь знает причину подскажите пожалуйста.
 
Последнее редактирование:

JustACat

Moderator
Команда форума
Покажите форму (HTML код конечно же), покажите, какой в итоге точно URL полностью прилетает в data (которую вы в итоге передаете в ParseURLtoStr).
Только реальные покажите, а не те, которые по идее туда должны прилететь.
 

Alex_S

New member
Судя по коду, заполняются только id и ssid? )

По коду есть замечания:
1. Функция strncpy не гарантирует, что результат будет терминирован нулем. Это значит, что лучше безусловно выходную строку терминировать ручками.
2. Выделяем под поля id и ssid по 32 байта, а копируем реально не более 16. Думаю, стоит привести в соответствие.
3. В функции ParseURLtoStr есть вывод отладки. Там все правильно выводится? В смысле - там мусор не наблюдается?

Думаю, лучше привести полный код (в текущем заполняются только два поля) и результат, который выводится в отладку. Тогда будет проще понять что именно там не так.
 

anakod

Moderator
Команда форума

Nail

New member
Собственно код html формы:
Код:
onst char httpform[] = "<!DOCTYPE html><html><head>\
        <meta http-equiv=\"Content-type\" content=\"text/html; charset=utf-8\"></head>\
        <title>ESP8266EX SETUP</title><body><h1>RFID reader setup</h1><form method=\"post\" action=\"\"><table>\
        <tr><td></td><td><input type=\"hidden\" name=\"__begin\" value=\"begin\"></td></tr>\
        <tr><td>Device ID:</td><td><input name=\"__devid\" maxlength=\"32\" SIZE=\"22\" value=\"1234\"></td></tr>\
        <tr><td>Wi-fi mode:</td><td><select name=\"__mode\">\
        <option value=\"0\">station</option><option value=\"1\">client</option></select></td></tr>\
        <tr><td>SSID:</td><td><input name=\"__ssid\" maxlength=\"32\" SIZE=\"22\" value=\"ESP8266\"></td></tr>\
        <tr><td>Password:</td><td><input name=\"__pwd\" maxlength=\"32\" SIZE=\"22\" value=\"00000000\"></td></tr>\
        <tr><td>ip addr:</td><td><input name=\"__ip\" maxlength=\"32\" SIZE=\"22\" value=\"192.168.0.1\"></td></tr>\
        <tr><td>netmask:</td><td><input name=\"__nm\" maxlength=\"32\" SIZE=\"22\" value=\"255.255.255.0\"></td></tr>\
        <tr><td>gateway:</td><td><input name=\"__gw\" maxlength=\"32\" SIZE=\"22\" value=\"192.168.0.1\"></td></tr>\
        <tr><td>srv ip addr:</td><td><input name=\"__host\" SIZE=\"10\" maxlength=\"32\" value=\"192.168.0.10\">\
        port:<input name=\"__port\" SIZE=\"1\" maxlength=\"5\" value=\"2323\"></td></tr>\
        <tr><td></td><td><input type=\"hidden\" name=\"__end\" value=\"end\"></td></tr>\
        </table><input type=submit value=\"apply\"></form></body></html>";
Данные которые поступают после отправки формы выложу чуть попозже т.к. сейчас некуда воткнуть (отсутствует com port).
По коду заполняются не только "__devid" и "__ssid":
Код:
ParseURLtoStr(ptr, user_config.id, "__devid=", '&', 16 );
ParseURLtoInt(ptr, user_config.mode, "__mode=" , '&', 2 );
ParseURLtoStr(ptr, user_config.ssid, "__ssid=" , '&', 32 );
ParseURLtoStr(ptr, user_config.pwd , "__pwd=" , '&', 64 );
ParseURLtoStr(ptr, user_config.ip, "__ip=" , '&', 16 );
ParseURLtoStr(ptr, user_config.nm, "__nm=" , '&', 16 );
ParseURLtoStr(ptr, user_config.gw,  "__gw=" , '&', 16);
ParseURLtoStr(ptr, user_config.host,"__host=" , '&', 16);
ParseURLtoInt(ptr, user_config.port, "__port="  , '&', 4);
В функции ParseURLtoStr в отладке выводится также мусор.
 
Последнее редактирование:

Nail

New member
Вот код:
Код:
#ifndef LWIP_POSIX_SOCKETS_IO_NAMES
#define LWIP_POSIX_SOCKETS_IO_NAMES 0
#endif

extern "C"
{
    #include "esp_common.h"
    #include "freertos/FreeRTOS.h"
    #include "freertos/task.h"
}

#include "fdvserial.h"
#include "fdvsync.h"
#include "fdvflash.h"
#include "fdvtask.h"
#include "fdvnetwork.h"
#include "user_config.h"

typedef struct {
    char id[32];
    int16_t mode;
    char ssid[32];
    char pwd[64];
    char ip[32];
    char nm[32];
    char gw[32];
    char host[32];
    int16_t port;
    uint32_t valid;
} user_config_t;

static user_config_t user_config;

const char ID[] = "ESP07";
const char SSID[] = "Test";
const char PASSWD[] = "test";
const char IP[] = "192.168.0.12";
const char NM[] = "255.255.255.0";
const char GW[] = "192.168.0.1";
const char HOST[] = "192.168.0.9";
const int PORT = 2323;

const char httpform[] = "<!DOCTYPE html><html><head>\
        <meta http-equiv=\"Content-type\" content=\"text/html; charset=utf-8\"></head>\
        <title>ESP8266EX SETUP</title><body><h1>RFID reader setup</h1><form method=\"post\" action=\"\"><table>\
        <tr><td></td><td><input type=\"hidden\" name=\"__begin\" value=\"begin\"></td></tr>\
        <tr><td>Device ID:</td><td><input name=\"__devid\" maxlength=\"32\" SIZE=\"22\" value=\"1234\"></td></tr>\
        <tr><td>Wi-fi mode:</td><td><select name=\"__mode\">\
        <option value=\"0\">station</option><option value=\"1\">client</option></select></td></tr>\
        <tr><td>SSID:</td><td><input name=\"__ssid\" maxlength=\"32\" SIZE=\"22\" value=\"ESP8266\"></td></tr>\
        <tr><td>Password:</td><td><input name=\"__pwd\" maxlength=\"32\" SIZE=\"22\" value=\"00000000\"></td></tr>\
        <tr><td>ip addr:</td><td><input name=\"__ip\" maxlength=\"32\" SIZE=\"22\" value=\"192.168.0.1\"></td></tr>\
        <tr><td>netmask:</td><td><input name=\"__nm\" maxlength=\"32\" SIZE=\"22\" value=\"255.255.255.0\"></td></tr>\
        <tr><td>gateway:</td><td><input name=\"__gw\" maxlength=\"32\" SIZE=\"22\" value=\"192.168.0.1\"></td></tr>\
        <tr><td>srv ip addr:</td><td><input name=\"__host\" SIZE=\"10\" maxlength=\"32\" value=\"192.168.0.10\">\
        port:<input name=\"__port\" SIZE=\"1\" maxlength=\"5\" value=\"2323\"></td></tr>\
        <tr><td></td><td><input type=\"hidden\" name=\"__end\" value=\"end\"></td></tr>\
        </table><input type=submit value=\"apply\"></form></body></html>";

//__id=&__mode=0&__ssid=dlink&__pwd=pass&ip=192.168.1.1&port=2323

//save form data to file system
//fdv::FlashFileSystem::save("def1", &dev_config, sizeof(MyConfig));

//load data
//fdv::FlashFileSystem::load("def1", &dev_config);

int ParseURLtoStr(char const *in, char *out, const char *searchstr, char delim, int maxLength)
{
    //int32_t len = read(str1, bufsize);
    char *data;
    data = strstr( in, searchstr );
    if(!data) return 0;
    data += strlen(searchstr);
    int i = 0;
    while( (data[i] != delim) && (i < maxLength ))i++;
    strncpy( out, data, i );
    printf("%s => %s\n", searchstr, out);
    return i;
}

int ParseURLtoInt( char const *in, int16_t out, const char *searchstr, char delim, int maxLength)
{
    //int32_t len = read(str1, bufsize);
    char *data;
    data = strstr( in, searchstr );
    if(!data) return 0;
    data += strlen(searchstr);
    int i = 0;
    char pbuf[maxLength];

    while( (data[i] != delim) && (i < maxLength))i++;
    strncpy( pbuf, data, i );
    out = atoi(pbuf);
    printf("%s => %d\n", searchstr, out);
    return i;
}

struct HttpHandler : public fdv::TCPConnectionHandler
{
    void write_http(const char *http)
    {
        char httplen[32];
        const char http200[] = "HTTP/1.1 200 OK\n";
        const char httptype[]= "Content-Type: text/html\n\n";
        sprintf(httplen, "%s%d%s", "Content-length: ", strlen(http), "\n");
        write( const_cast<char*>(http200) );
        write( const_cast<char*>(httplen));
        write( const_cast<char*>(httptype ));
        write( const_cast<char*>(http), strlen(http));
    }

    void ICACHE_FLASH_ATTR connectionHandler()
    {
        int len;
        char *data = new char[128];
        int i, j, m, n, x, y, z, s, w;

        memset((void*)&user_config, 0, sizeof(user_config_t));

        while (isConnected())
        {
            write_http(httpform);
           
            len = read(data, 128);

            i = ParseURLtoStr(data, user_config.id, "__devid=", '&', 16 );
            j = ParseURLtoInt(data, user_config.mode, "__mode=", '&', 1 );
            m = ParseURLtoStr(data, user_config.ssid, "__ssid=", '&', 32 );
            n = ParseURLtoStr(data, user_config.pwd, "__pwd=", '&', 64 );
            x = ParseURLtoStr(data, user_config.ip, "__ip=", '&', 16 );
            y = ParseURLtoStr(data, user_config.nm, "__nm=", '&', 16 );
            z = ParseURLtoStr(data, user_config.gw, "__gw=", '&', 16);
            s = ParseURLtoStr(data, user_config.host,    "__host=", '&', 16);
            w = ParseURLtoInt(data, user_config.port,    "__port=", '&', 4);
        }

        if(i && j && m && n && x && y && z && s && w)
            fdv::FlashFileSystem::save("conf", &user_config, sizeof(user_config_t));

        delete[] data;
        //printf("disconnected\n\r");
    }
};


struct Task1 : fdv::Task
{

    Task1(fdv::Serial* serial)
        : fdv::Task(400), m_serial(serial)
    {
    }

    fdv::Serial* m_serial;

    void ICACHE_FLASH_ATTR exec()
    {
        if ( fdv::FlashFileSystem::load("conf", &user_config))
        {
            fdv::WiFi::setMode(static_cast<fdv::WiFi::Mode>(user_config.mode));
            if(user_config.mode == fdv::WiFi::Client)
            {
                fdv::WiFi::configureClient(user_config.ssid, user_config.pwd);
                fdv::IP::configureStatic(fdv::IP::ClientNetwork, user_config.ip, user_config.nm, user_config.gw);
            }
            else
            {
                fdv::WiFi::configureAccessPoint(user_config.ssid, user_config.pwd, 9);
                fdv::IP::configureStatic(fdv::IP::AccessPointNetwork, user_config.ip, user_config.nm, user_config.ip);
            }
        }
        else
        {
            fdv::WiFi::setMode(fdv::WiFi::Client);
            fdv::WiFi::configureClient(SSID, PASSWD);
            fdv::IP::configureStatic(fdv::IP::ClientNetwork, IP, NM, GW);
        }


        //new fdv::TCPClient<MyTCPConnectionHandler>(2323);
        new fdv::TCPServer<HttpHandler>(80);

        while (1)
        {
        }
    }
};


struct MainTask : fdv::Task
{
    void ICACHE_FLASH_ATTR exec()
    {
        //fdv::DisableStdOut();
        fdv::DisableWatchDog();

        fdv::HardwareSerial serial(9600, 128);

        Task1 task1(&serial);

        suspend();
    }
};

extern "C" void /*ICACHE_FLASH_ATTR*/ user_init(void)
{
    new MainTask;    // never destroy!
}
 
Последнее редактирование:

Victor

Administrator
Команда форума
Nail, можно выбрать язык программирования, когда вставляете код через панель инструментов, или прописать явно прямо в теге [ CODE=C++] (без пробела). Аналогично и HTML и другие.
Попробуйте отредактировать ваши сообщения - код станет намного более читабельным и симпатичным :) Также удобно "прятать" большие куски кода в спойлер - тогда можно в один пост много чего разместить. Удачи!
 

Alex_S

New member
Интересно посмотреть что данный код выдает в отладку.

У меня результирующая строка вышла такой:
Код:
__begin=begin&__devid=1234&__mode=0&__ssid=ESP8266&__pwd=00000000&__ip=192.168.0.1&__nm=255.255.255.0&__gw=192.168.0.1&__host=192.168.0.10&__port=2323&__end=end
Это около 160 байт, что больше, чем читается за раз (128байт). Значит, все параметры за раз вычитаны не будут. Но код требует, чтобы все параметры были вычитаны за раз:
Код:
if(i && j && m && n && x && y && z && s && w)
а все переменные i, j, и т.д. заполняются в каждом проходе цикла, в том числе нулями, если в данном проходе нужный тэг не обнаружен. Стоит обратить на это внимание.

Еще в функциях парсера не проверяется конец строки для входного буфера:
Код:
while( (data[i] != delim) && (i < maxLength ))i++;
А значит, мы можем выйти за пределы буфера, если нужный тэг вошел в буфер не полностью.

А насчет мусора - без входной строки и результата мало что можно сказать. Предварительно - должно работать нормально, если входная строка соответствует ожиданиям.
 

Nail

New member
Я пробовал буфер и 256 байт и 512 и 1024 байт, все равно несколько параметров затираются мусором.
 

Nail

New member
Вот отладочный вывод через терминал если просто посимвольно выводить данные не парся их:
Terminal log file
Date: 30.03.2015 - 16:18:44
-----------------------------------------------
POST / HTTP/1.1
Accept: image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, application/x-ms-application, application/x-ms-xbap, application/vnd.ms-xpsdocument, application/xaml+xml, */*
Referer: http://192.168.0.12/
Accept-Language: ru
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate
Host: 192.168.0.12
Content-Length: 154
Connection: Keep-Alive
Cache-Control: no-cache

__begin=begin&__id=test&__mode=1&__ssid=test&__pwd=qwerty&__ip=192.168.0.10&__nm=255.255.255.0&__gw=192.168.0.1&__host=192.168.0.11&__port=5555&__end=end

-----------------------------------------------
Date: 30.03.2015 - 16:19:22
End log file
 

Nail

New member
Вот отладочный вывод после вызова функций:

Код:
ParseURLtoStr(ptr, user_config.id, "__devid=", '&', 16 );
ParseURLtoInt(ptr, user_config.mode, "__mode=", '&', 1 );
ParseURLtoStr(ptr, user_config.ssid, "__ssid=", '&', 32 );
ParseURLtoStr(ptr, user_config.pwd, "__pwd=", '&', 64 );
ParseURLtoStr(ptr, user_config.ip, "__ip=", '&', 16 );
ParseURLtoStr(ptr, user_config.nm, "__nm=", '&', 16 );
ParseURLtoStr(ptr, user_config.gw, "__gw=", '&', 16);
ParseURLtoStr(ptr, user_config.host,    "__host=", '&', 16);
ParseURLtoInt(ptr, user_config.port,    "__port=", '&', 4);
Terminal log file
Date: 30.03.2015 - 16:31:18
-----------------------------------------------
__id= => TEST123
__mode= => 1
__ssid= => ESP07
__pwd= => qwerty
__ip= => 192.168.1.10
__nm= => 255.255.255.0
__gw= => 192.
__ssid= => ESP07
__pwd= => qwerty
__ip= => 192.168.1.10
__nm= => 255.255.255.0
__gw= => 192.°…я?
__host= => 192.168.1.11
__port= => 2323
__ssid= => ESP07
__pwd= => qwerty
__ip= => 192.168.1.10
__nm= => 255.255.255.0
__gw= => 192.шяя?°‡
__host= => 192.168.1.11
__port= => 2323

-----------------------------------------------
Date: 30.03.2015 - 16:32:28
End log file
 

Alex_S

New member
При нескольких выполнениях одного и того же - мусор появляется в одних и тех же местах или каждый раз по-разному?
 

Nail

New member
На данном устройстве запущен tcp сервер на порту 80. При входе на http://ip адресс устройства далее отображается html форма, ее и отправляю. Тут еще нюанс почемуто некоторые позиции в выводе повторяются по 2-3 раза. Мусор может появлятся в разных местах в зависимости от размера принимаемого буфера.
 

pvvx

Активный участник сообщества
1) Делаем разбивку входных входных данных по 128 байт:
Код:
POST / HTTP/1.1  Accept: image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-exce
l, application/vnd.ms-powerpoint, application/msword, application/x-ms-application, application/x-ms-xbap, application/vnd.ms-xp
sdocument, application/xaml+xml, */*  Referer: http://192.168.0.12/  Accept-Language: ru  User-Agent: Mozilla/4.0 (compatible; M
SIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)  Content-Type: application
/x-www-form-urlencoded  Accept-Encoding: gzip, deflate  Host: 192.168.0.12  Content-Length: 154  Connection: Keep-Alive  Cache-C
ontrol: no-cache    __begin=begin&__id=test&__mode=1&__ssid=test&__pwd=qwerty&__ip=192.168.0.10&__nm=255.255.255.0&__gw=192.168.
0.1&__host=192.168.0.11&__port=5555&__end=end
Видим, что дырка приходиться за символом "__gw=192.168." . Из чего следует, что где-то при копировании строк из форумных форм я упустил 4 байта :)
Т.е. там и наблюдаем неизвестные символы, т.к. :
Name: strncpy
Prototype: char * strncpy (char *restrict to, const char *restrict from, size_t size)
Description:
This function is similar to strcpy but always copies exactly size characters into to.

If the length of from is more than size, then strncpy copies just the first size characters. Note that in this case
there is no null terminator written into to.

If the length of from is less than size, then strncpy copies all of from, followed by enough null characters to add
up to size characters in all. This behavior is rarely useful, but it is specified by the ISO C standard.

The behavior of strncpy is undefined if the strings overlap.

Using strncpy as opposed to strcpy is a way to avoid bugs relating to writing past the end of the allocated
space for to. However, it can also make your program much slower in one common case: copying a string
which is probably small into a potentially large buffer. In this case, size may be large, and when it is, strncpy will
waste a considerable amount of time copying null characters.
Header files:
string.h

И дубли найденных ID - от туда-же, т.к. буфер многократно дублируется в памяти и не имеет терминаторов строк - поиск идет в памяти за буфером... data =strstr( in, searchstr ); может доехать до буфера LwIP :) Т.е. поиск ID идет по всей памяти ESP8266, вроде, пока не встретит '\0' :)
2) При задании пароля типа __gw или любого значения переменной, похожей на идентификатор, у данного "парсера" получим на выходе белиберду...

Ещё интересно - автору не жалко iram, куда пойдет код из библиотеки string ? Почему было не изменить, к примеру, strncpy на os_strncpy или ets_strncpy находящиеся в ROM-BIOS ESP8266 ?
 
Последнее редактирование:

Nail

New member
Вообщем вроде бы проблема решилась.
Убрав из обработчика приема сообщений блок:
Код:
while(isConnected())
{
}
перестали дублироваться записи.
Увеличил буфер приема сообщений до 1024 байт(временное решение), так чтобы нужные мне данные не форматировались на куски.
Функцию strncpy заменил на более удачный аналог от sokel
При заполнении полей формы в виде идентификатора __pwd, или любых других, проблем не наблюдалось, т.е. вводимые значения сохраняются в том же виде, без мусора.
Всем кто откликнулся большое спасибо.
 
Последнее редактирование:

Alex_S

New member
А что насчет терминирования строки, в которой идет поиск? Добавил? Если нет - то нужно добавить обязательно!
 

pvvx

Активный участник сообщества
Теперь уже добавил, спс.
А где URL decode? Передаваемые строки при путешествии по сети до модуля через прокси могут подвергнуться url-кодированию. Да и как задать в пароле символы, не допустимые HTTP заголовках?
Аналогично заголовок HTTP запроса может быть разбит на куски (несколько пакетов) и придти поэтапно... Драйвер системы, отправляющей заголовок, тоже может выслать его кусками, если видит, что звено в сети имеет малый размер WIN стека или RSS (mtu) ... Эксплорер тоже разбивает заголовок на несколько пакетов, когда ему захочется... На то оно и TCP :)
Ну и замена strncpy на что-то другое не спасет от выбранного ошибочного алгоритма - искать совпадения для заголовков ID переменных во всем приемном потоке. Необходимо парсить весь заголовок HTTP, хотя-бы построчно, до \r\n и найдя параграф передачи контекста парсить его по разделителям, а уже далее по именам ID переменных.
 
Последнее редактирование:

Alex_S

New member
Если я не ошибаюсь, конкретно в данном случае строка приходит в теле сообщения, а не в заголовке. А значит, кодированиям не подвержена. По-крайней мере так было у меня при проверке.
 
Сверху Снизу