В уроке расскажу о принципе передачи данных от клиента WEB-серверу с помощью GET-запросов. Разработаем несколько вариантов учебных серверов с управлением из окна браузера.
Предыдущий урок Список уроков Следующий урок
Часто возникает задача не только получать информацию с WEB-сервера, но и передать на него данные. Это может быть управление физическими устройствами, подключенными к серверу, регистрационные данные, файлы для хранения, обратная связь с пользователями и т.п. Примеров множество.
Сделать это можно с помощью GET и POST-запросов. В этом уроке будем говорить о GET-запросах.
GET-запросы.
В предыдущем уроке мы разобрались, что GET-запрос позволяет получить данные из указанного ресурса.
Для пользователя это выглядит так.
- Он набираем в адресной строке браузера информацию о запрашиваемом ресурсе, т.е. идентификатор ресурса (URI).
- Браузер посылает запрос на сервер по указанному адресу.
- Сервер присылает в ответ нужную информацию.
GET-запрос это передача данных через идентификатор ресурса (URI). Для пользователя это посылка данных непосредственно в адресной строке браузера.
Давайте разберемся, что мы там передаем.
URI, URL и URN.
Подробнее об URI и его составляющих.
URI - (Uniform Resource Identifier) унифицированный идентификатор ресурса. Определяет полную адресную информацию для доступа к ресурсу. Т.е. к какому WEB-серверу надо обратиться с запросом, и что с этого сервера получить. В общем случае URI состоит из URL и URN.
URL – (Uniform Resource Locator) унифицированный определитель местонахождения ресурса. Это адрес ресурса в Интернете (IP-адрес или доменное имя) и способ обращения к нему. Определяет местонахождения сервера в глобальной сети.
URN – (Unifrorm Resource Name) унифицированное имя ресурса. Идентифицирует ресурс в определенном логическом пространстве, не указывая его местоположение. В нашем случае определяет, что конкретно мы запрашиваем с WEB-сервера.
Например, такой URI:
http://mypractic.ru/urok-70-protokol-http-sozdanie-web-servera-na-arduino-ispolzovani-html-koda.html
означает, что мы хотим:
- по протоколу HTTP;
- послать запрос на сервер с доменным именем mypractic.ru;
- на получение файла urok-70-protokol-http-sozdanie-web-servera-na-arduino-ispolzovani-html-koda.html.
Думаю, вы поняли, что в этом URI:
- URL - http://mypractic.ru/
- URN - urok-70-protokol-http-sozdanie-web-servera-na-arduino-ispolzovani-html-koda.html
К WEB-серверу запрос попадает по IP-адресу или доменному имени из URI.
- Для доставки запроса на сервер используется адресная информация URL.
- URN игнорируется сетью, он интерпретируется сервером.
Т.е. все, что мы в адресной строке браузера после IP-адреса или доменного имени отделим символом ”/” будет доставлено на сервер. Эта информация никак не повлияет на передачу запроса и останется в URI стартовой строки HTTP-запроса.
Так почему бы не использовать URN для передачи данных от клиента на WEB-сервер. Просто сервер должен понимать, что это не запрос ресурса, а информация для него. Но это уже в наших руках.
Давайте проверим вышесказанное с помощью первой программы из предыдущего урока. Напомню, что она выводит в последовательный порт все, что поступает на сервер.
Я набрал URI: http://192.168.1.10/test_URN_for_GET_request
Сервер принял URN: test_URN_for_GET_request.
Принято набор параметров, передаваемых на сервер, начинать с символа ”?” и разделять знаком ”&”. Сами параметры записываются в формате: параметр=значение. Это правило обязательно при обработке запроса на языке PHP.
http://site.com/register.php?fname=Ivan&name=Ivanov&age=30
К недостаткам GET-запросов можно отнести.
- Количество передаваемых данных всегда ограничено со стороны сервера и, как правило, не превышает 1024. Конечно, передать изображение, большие файлы таким образом нельзя.
- Некоторые браузеры некорректно передают кириллические символы. Лучше в GET-запросах их не использовать. Можно передавать коды символов. Для этого используется знак ”%”, за ним должен следовать шестнадцатеричный код.
- Не все символы можно использовать. Например, ”&” и ”?” зарезервированы, их передавать нельзя. Выход – передавать коды символов.
- В GET-запросах строка информации находится в открытом виде. URI не является закрытой информацией и часто сохраняется в различных логах, историях браузеров и т.п. Поэтому передача секретных данных через GET-запросы недопустима.
Несмотря на недостатки, передача данных на сервер через GET-запросы используется довольно часто. В некоторых случаях другого варианта просто нет или он достаточно сложен.
Например, при регистрации пользователя на сайте ему приходит письмо со ссылкой активации аккаунта. Эта ссылка содержит идентификатор аккаунта, т.е. является GET-запросом.
Еще пример. В личный кабинет на сайте я вхожу по ссылке, содержащей номер моего аккаунта: http://mypractic.ru/account?user=1
Реализация сервера с управлением от GET-запросов.
По аналогии с предыдущим уроком подключим светодиод ко 2 выводу платы. Наша учебная цель – создать WEB-сервер, который позволяет управлять этим светодиодом с помощью любого браузера.
Давайте начнем с того, что из входного запроса от клиента выделим URN. Мы сохраним его на сервере отдельной строкой и передадим обратно клиенту. Для контроля еще выведем URN в последовательный порт.
Возьмем за основу скетч WEB-сервера sketch_70_2 из предыдущего урока.
Добавим переменные:
char urnFromRequest[51]; // строка URN из запроса
boolean urnReceived= false; // признак URN принят
unsigned int indUrn; // адрес в строке URN
Для приема URN предназначена текстовая строка urnFromRequest. Введем ограничение на длину URN – 50 символов, поэтому зарезервируем на нее 51 символ. Лишний символ на признак конца строки.
В цикл приема символов заголовка вставим блок выделения URN.
// прием URN
if( urnReceived == false ) {
if( indUrn == 0xffff ) {
// пропуск метода
if( tempChar == '/' ) indUrn=0;
}
else {
// запись строки
if( tempChar == ' ' ) {
// URN закончен
urnFromRequest[indUrn]=0;
urnReceived = true;
}
else {
// загрузка символа URN в строку
urnFromRequest[indUrn] = tempChar;
indUrn++;
if( indUrn > 49 ) {
// переполнение
urnFromRequest[50]=0;
urnReceived = true;
}
}
}
}
В нем:
- Проверяем, не закончился ли URN.
- При начальном значении индекса строки indUrn равном 0xffff, пропускаем символы метода, ждем начала символов URN.
- Если встретился пробел ' ', завершаем прием URN.
- Загружаем символы URN в текстовую строку urnFromRequest.
В ответе клиенту передаем строку URN.
client.println("<html>");
client.println(urnFromRequest);
client.println("</html>");
Передаем URN в последовательный порт.
Serial.println(urnFromRequest);
Чтобы пунктуально перечислить все изменения в скетче предыдущего урока добавлю, что в месте обнаружения нового клиента надо установить значения переменных.
if (client) {
flagEmptyLine = true;
urnReceived = false;
indUrn=0xffff;
Полностью скетч программы можно загрузить по ссылке:
Проверим. Послал запрос.
Получил URN в браузере и в мониторе последовательного порта.
Дальше будем работать со строкой urnFromRequest.
Управление светодиодом сервера из адресной строки браузера.
Для начала сделаем самый простой вариант. Будем управлять светодиодом из адресной строки браузера. Создадим WEB-сервер, который:
- Получает запрос от клиента.
- При URN, равном “ON”:
- включает светодиод;
- передает клиенту сообщение ”Светодиод включен”.
- При URN, равном “OFF”:
- выключает светодиод;
- передает клиенту сообщение ”Светодиод выключен”.
Попросту говоря, в адресной строке браузера надо набрать:
- для включения светодиода - http://192.168.1.10/ON;
- для выключения - http://192.168.1.10/OFF.
За основу возьмем предыдущий скетч. К нему необходимо добавить блок сравнения строки urnFromRequest со строками ”ON” и ”OFF”. И в зависимости от результата зажигать или гасить светодиод.
Воспользуемся стандартной функцией сравнения строк strcmp().
int strcmp (const char * S1, const char * S2)
Функция сравнивает две строки с указателями S1 и S2.
Функция возвращает:
- 0, если строки равны;
- - 1, если символы строки S1 раньше встречаются в алфавите, чем символы строки S2;
- 1, если символы строки S2 раньше встречаются в алфавите, чем символы строки S1.
В HTML-ответ клиенту вставим блок, который управляет светодиодом и формирует сообщения.
client.println(F("<html>"));
if( strcmp(urnFromRequest, "ON") == 0) {
digitalWrite(2, HIGH);
client.println(F("Светодиод включен"));
}
else if( strcmp(urnFromRequest, "OFF") == 0) {
digitalWrite(2, LOW);
client.println(F("Светодиод выключен"));
}
else client.println(F("Недопустимая команда"));
client.println("</html>");
Полностью программу можно загрузить по ссылке:
Проверяем. Все работает.
Управление светодиодом сервера из окна браузера.
Управлять светодиодом из адресной строки браузера не очень удобно. Давайте создадим WEB-сервер, позволяющий включать и выключать светодиод из окна браузера с помощью объектов HTML.
Все изменения программы коснутся только HTML-блока ответа клиенту. Мы будем передавать браузеру с сервера графические HTML-компоненты, которые он отобразит на экране компьютера. Эти компоненты должны формировать известные нам URI:
- для включения светодиода - http://192.168.1.10/ON;
- для выключения - http://192.168.1.10/OFF.
Самый простой вариант – ссылки.
Создадим их с помощью HTML-редактора. Я, как в предыдущем уроке, использовал онлайн HTML-редактор Vulk. Вы можете воспользоваться любым другим редактором или непосредственно использовать код HTML.
Я упростил HTML-код, убрал блоки div. Получилось:
<font size="5">Светодиод выключен</font>
<br>
<br>
<font size="4"><a href="http://192.168.1.10/ON">Включить светодиод</a></font><br>
<br>
<font size="4"><a href="http://192.168.1.10/OFF">Выключить светодиод</a></font>
Экранировал кавычки, сделал проверку состояния светодиода и вставил код в блок HTML-ответа.
// управление светодиодом
if( strcmp(urnFromRequest, "ON") == 0) digitalWrite(2, HIGH);
else if( strcmp(urnFromRequest, "OFF") == 0) digitalWrite(2, LOW);
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
client.println(F("<html>"));
client.println(F("<font size=\"5\">"));
// состояние светодиода
if( digitalRead(2) == HIGH ) client.println(F("Светодиод включен"));
else client.println(F("Светодиод выключен"));
client.println(F("</font><br><br><font size=\"4\"><a href=\"http://192.168.1.10/ON\">Включить светодиод</a></font><br><br><font size=\"4\"><a href=\"http://192.168.1.10/OFF\">Выключить светодиод</a></font>"));
client.println("</html>");
Вот полный скетч:
Обращаемся к серверу http://192.168.1.10 без URN.
Теперь управлять светодиодом можно нажатием на ссылки.
Украшать интерфейс с пользователем можно до бесконечности. Давайте сделаем управление кнопками и остановимся на этом.
Заменим только одну строку.
client.println(F("</font><br><br><font size=\"4\"><a href=\"http://192.168.1.10/ON\"><button><font size=\"4\">Включить светодиод</font></button></a></font><br><br><font size=\"4\"><a href=\"http://192.168.1.10/OFF\"><button><font size=\"4\">Выключить светодиод</font></button></a></font>"));
Полный скетч:
Интерфейс с пользователем стал ближе к общепринятому.
Можно добавить таблицы, красивые кнопки, раскрасить поля, нарисовать светодиод и зажигать его… Это не сложно. Учите HTML.
Как видите, все достаточно просто. Надеюсь, выражение WEB-сервер превратилось для вас из чего-то пугающе сложного в вполне доступную для реализации задачу.
В следующем уроке продолжим тему HTTP-протокола. Научимся использовать для передачи данных на сервер POST-запросы. Разработаем простой WEB-клиент.
Эдуард Благодарю вас, понравился ваш урок.
Можно и так.
if (memcmp(urnFromRequest, "ON", 2) == 0)Serial.println(1);
if (memcmp(urnFromRequest, "OFF", 3) == 0)Serial.println(0);
Здравствуйте, Эдуард! А что означает tempChar и чему эта переменная равна?
Здравствуйте!
Это промежуточная переменная, в которой хранится очередное данное от клиента: tempChar = client.read();
у меня всё где прописан IP не работает, просто маршрутизатор не видит IP, а с DHCP скетчем всё работает, подскажите чито делать ?
Здравствуйте!
Может неправильный IP-адрес указываете.
Здравствуйте, Эдуард.
Помогите, пожалуйста, разобраться, никак не могу сообразить.
Я хочу сделать сервер для контроля и управления несколькими контроллерами Arduino подключенными по ModBUS, управление сервером через WEB-интерфейс.
Сделал локальный контроллер из урока 59 со скетчем 59_1. В нем сделал включение/выключение светодиода через регистр ModBus по клику кнопкой. В скетч 71_4 добавил блоки из 59_2. Для наглядности подключил светодиод к серверу, чтобы он повторял светодиод с локального контроллера.
При включении/выключении светодиода кнопкой на локальном контроллере на сервере светодиод тоже включается/выключается. Если нажать кнопку на WEB-странице, светодиод на локальном контроллере также реагирует, но если кликнуть кнопкой на локальном контроллере, выключится(включится) и тут же поменяет состояние обратно. От сервера циклически приходят последние данные. Еще пришлось сделать отдельный массив регистров для передачи на локальный контроллер, иначе, при использовании одного и того же регистра для приема и передачи, светодиоды на локальном контроллере и на сервере поочередно мигали.
Как сделать чтобы данные регистра светодиода передавались на локальный контроллер единожды, только после нажатия кнопки в WEB-интерфейсе? И чтобы информация на WEB-странице о состоянии светодиода на локальном контроллере обновлялась сама? Если добавить строку client.println(F(«Refresh: 2»)); в заголовки, WEB-страница обновляется с последним значение URN, это равносильно нажатию на кнопку. Можно ли как-то сделать автоматическое
обновление WEB-страницы только по IP адресу без URN?
Здравствуйте!
Надо использовать Javascript. Посмотрите в последней статье из рубрики Умное растениеводство. Там в программе контроллера гроубокса обновление данных происходит по таймеру Javascript. Происходит обновление не всей страницы, а только нужных данных. Такой же обработчик можно привязать к кнопкам. Тогда нажатие на кнопку будет вызывать одиночное действие, не связанное с другими данными страницы.