[Перевод] Starting Electronics: руководство по веб-серверам на Arduino. Часть 8. Веб-сервер с SD картой и ссылки на страницы

sggneyazufubnazazkkgv2urwd8.jpeg

От переводчика. Всё интереснее и интереснее. В этом уроке рассматривается хранение двух файлов на SD карте памяти веб-сервера и создание ссылок в HTML коде веб-страниц на эти файлы, а также загрузка этих страниц при нажатии на ссылки. Фактически мини-прототип сайта, который вы можете самостоятельно развивать и модернизировать.

Создание ссылок в HTML


В HTML ссылки создаются при помощи тега. Текст между открывающим тегом и закрывающим тегом становится «кликабельной» ссылкой.

Значение атрибута href тега должно содержать имя файла веб-страницы на которую делается ссылка, например:

Go to page 2.


Эта HTML строка создаст текстовый абзац в котором »page 2» станет ссылкой на файл с именем page2.htm.

Файл page2.htm должен присутствовать на карте памяти и находиться в том же каталоге, что и страница, содержащая ссылку на него.

Примеры HTML файлов


В качестве примера в этом уроке будут использоваться два HTML файла. Они должны быть сохранены на microSD карте памяти, а сама карта должна быть вставлена в соответствующий разъём на плате Ethernet Shield.

Главная страница index.htm, которая будет загружаться первой с сервера, содержит следующий HTML код:



    
        Arduino SD Card Web Page
    
    
        

Arduino SD Card Page with Link

Go to page 2.


Эта страница ссылается на вторую страницу с именем page2.htm:



    
        Arduino SD Card Web Page 2
    
    
        

Arduino SD Card Page 2

Go back to main page.


Здесь страница page2.htm (обратно) ссылается на главную страницу index.htm.

Создайте два файла index.htm и page2.htm с указанным выше содержим и скопируйте их на microSD карту памяти. Затем вставьте карту в разъём на Ethernet Shield.

Предварительно работу этих страниц можно протестировать на компьютере (поместив оба файла в одну папку на жестком диске). Откройте файл index.htm в браузере и щёлкните на ссылку — при этом должен открыться файл page2.htm. При нажатии на ссылку на странице page2.htm браузер должен вернуться к странице index.htm.

В этом видео показано как скопировать файлы страниц на SD карту и как веб-сервер работает с ними. Код скетча и его описание будут даны ниже.


HTTP запросы страниц


Когда веб-браузер запрашивает главную страницу с веб-сервера Arduino, он отправляет HTTP запрос, подобный этому:

GET / HTTP/1.1
Host: 10.0.0.20
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:19.0) Gecko/20100101 Firefox/19.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-ZA,en-GB;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Connection: keep-alive


Мы уже видели такие HTTP запросы в предыдущих частях этого руководства.

При нажатии на ссылку на странице index.htm (это ссылка на страницу page2.htm), браузер отправляет Arduino серверу следующий запрос:

GET /page2.htm HTTP/1.1
Host: 10.0.0.20
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:19.0) Gecko/20100101 Firefox/19.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-ZA,en-GB;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Referer: http://10.0.0.20/
Connection: keep-alive


В первом случае производится запрос (GET /) корневого файла (по умолчанию это index.htm).

Во втором случае производится запрос определённой страницы (GET /page2.htm).

Поскольку функционал Arduino веб-сервера определяем мы, то в скетче мы должны предусмотреть проверку HTTP запроса и определить запрашивается ли корневой или какой-то другой файл.

Скетч Arduino для работы с двумя веб-страницами


Скетч, представленный ниже, это модифицированная версия скетча из 2-й части этого руководства.

/*--------------------------------------------------------------
  Скетч:      eth_websrv_SD_link

  Описание:  Arduino веб-сервер с двумя страницами на SD карте памяти, имеющими ссылки друг на друга.
  
  Оборудование: контроллер Arduino Uno, плата Ethernet Shield, microSD карта памяти 2 Гб, отформатированная в FAT16
                
  Программное обеспечение: среда разработки Arduino IDE
  
  Ссылки:
    - WebServer example by David A. Mellis and modified by Tom Igoe
    - SD card examples by David A. Mellis and Tom Igoe
    - Ethernet library documentation: http://arduino.cc/en/Reference/Ethernet
    - SD Card library documentation: http://arduino.cc/en/Reference/SD

  Дата создания:         2 марта 2013
  Изменено:     14 июня 2013
 
  Автор:       W.A. Smith, http://startingelectronics.org
--------------------------------------------------------------*/

#include 
#include 
#include 

// Размер буфера для HTTP запроса
#define REQ_BUF_SZ   20

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
IPAddress ip(192, 168, 0, 20); // IP-адрес (нужно изменить на актуальный для вашей сети)
EthernetServer server(80);

File webFile; // для работы с файлами на SD карте
char HTTP_req[REQ_BUF_SZ] = {0}; // HTTP запрос, сохраняемый как null terminated строка
char req_index = 0; // индех HTTP_req буфера

void setup() {
    // Выключение Ethernet чипа
    pinMode(10, OUTPUT);
    digitalWrite(10, HIGH);
    
    Serial.begin(115200);
    
    // Инициализация SD карты
    Serial.println("Initializing SD card...");
    if (!SD.begin(4)) {
        Serial.println("ERROR - SD card initialization failed!");
        return;
    }
    Serial.println("SUCCESS - SD card initialized.");
    // Поиск файла index.htm
    if (!SD.exists("index.htm")) {
        Serial.println("ERROR - Can't find index.htm file!");
        return;
    }
    Serial.println("SUCCESS - Found index.htm file.");

    Ethernet.begin(mac, ip);
    server.begin();
}

void loop() {
    EthernetClient client = server.available();  // try to get client

    if (client) {
        boolean currentLineIsBlank = true;
        while (client.connected()) {
            if (client.available()) {
                char c = client.read();
                // оставить последний элемент массива 0 (null terminate строка)
                if (req_index < (REQ_BUF_SZ - 1)) {
                    HTTP_req[req_index] = c; // сохраняем символ HTTP запроса
                    req_index++;
                }
                Serial.print(c);    // печатаем HTTP запрос в Serial
                if (c == '\n' && currentLineIsBlank) {
                    client.println("HTTP/1.1 200 OK");
                    client.println("Content-Type: text/html");
                    client.println("Connnection: close");
                    client.println();
                    // Открываем запрошенный файл
                    if (StrContains(HTTP_req, "GET / ") || StrContains(HTTP_req, "GET /index.htm")) {
                        webFile = SD.open("index.htm");
                    }
                    else if (StrContains(HTTP_req, "GET /page2.htm")) {
                        webFile = SD.open("page2.htm");
                    }
                    // Посылаем веб-страницу клиенту
                    if (webFile) {
                        while(webFile.available()) {
                            client.write(webFile.read());
                        }
                        webFile.close();
                    }
                    // Обнуляем буфер
                    req_index = 0;
                    StrClear(HTTP_req, REQ_BUF_SZ);
                    break;
                }
                if (c == '\n') {
                    currentLineIsBlank = true;
                } 
                else if (c != '\r') {
                    currentLineIsBlank = false;
                }
            } // end if (client.available())
        } // end while (client.connected())
        delay(1);
        client.stop();
    } // end if (client)
}

// Обнуляем массив
void StrClear(char *str, char length) {
    for (int i = 0; i < length; i++) {
        str[i] = 0;
    }
}

// Ищем совпадение в запросе
char StrContains(char *str, char *sfind) {
    char found = 0;
    char index = 0;
    char len;

    len = strlen(str);
    
    if (strlen(sfind) > len) {
        return 0;
    }
    while (index < len) {
        if (str[index] == sfind[found]) {
            found++;
            if (strlen(sfind) == found) {
                return 1;
            }
        }
        else {
            found = 0;
        }
        index++;
    }

    return 0;
}


Примечание. Не забудьте изменить IP-адрес, указанный в скетче, на актуальный для вашей сети.

Ниже описаны изменения по сравнению с исходной версией из 2-й части руководства.

HTTP запрос


Скетч был изменен для сохранения HTTP запроса в строке HTTP_req. Затем эту строку можно использовать для поиска нужных данных (имени запрашиваемого файла).

HTTP запрос выводится в Serial и эту информацию можно использовать для диагностики и отладки работы кода.

Посылка запрошенной веб-страницы


После того, как Arduino сервер получил HTTP запрос от браузера, он сначала отвечает стандартным HTTP заголовком, а затем отправляет саму запрошенную страницу.

Фрагмент кода, который осуществляет выбор веб-страницы для отправки:

// Открываем запрошенный файл
if (StrContains(HTTP_req, "GET / ") || StrContains(HTTP_req, "GET /index.htm")) {
    webFile = SD.open("index.htm");
}
else if (StrContains(HTTP_req, "GET /page2.htm")) {
    webFile = SD.open("page2.htm");
}


Здесь открывается файл index.htm или файл page2.htm с SD карты памяти. Сам код, который отправляет файл браузеру, такой же, как во 2-й части этого руководства.

Для выбора того или иного файла, функцией StrContains () просматривается строка HTTP_req, содержащая запрос. Если запрос содержит «GET /», то выбирается корневой файл index.htm.

Если строка HTTP запроса содержит «GET /page2.htm», то будет открыт и отправлен браузеру файл page2.htm.

Ссылка на странице page2.htm указывает на index.htm, а не на »/». По этой причине в коде необходимо проверить, содержит ли HTTP запрос текст «GET /» или текст «GET /index.htm». Реализацию этого вы можете увидеть в вышеприведенном фрагменте кода.

Улучшения скетча


Скетч из этого урока демонстрирует механизм открытия ссылок на страницы, поэтому он был сделан максимально простым. Конечно же этот код может быть улучшен, например, можно сразу извлекать из запроса имя файла (страницы) и предусмотреть обработку ошибок, когда страница, запрашиваемая браузером, отсутствует на SD карте и т. д.

От переводчика о 8-й части


Фактически, всё, что написано в в этом уроке, можно свести к следующему:

GET запрос сохраняется в буфер, далее в этом запросе находится информация о запрошенном файле, на основании найденной информации открывается тот или иной файл, который затем передаётся браузеру.

Отсюда следует, что при помощи небольшой модификации кода можно масштабировать этот скетч для работы с любым количеством веб-страниц на SD карте памяти.

Часть 1, часть 2, часть 3, часть 4, часть 5, часть 6, часть 7.

mxuanbovcusqgmqdgugvpnql8vq.jpeg

© Habrahabr.ru