Вопрос по протоколу HTTP. Браузер не воспринимает ответ от железки.

Всем привет.

Ситуация такая: железка с помощью W5100 общается по сети с компом.
Протокол UDP поднялся почти без вопросов и нормально работает. Проблема возникла когда на другом порту поднял TCP-сервер для отдачи html-странички.
Данные принимаются от компа и выдается ответ, но браузер данный ответ не воспринимает и не выводит.

Вот запрос приходящий от браузера:



Вот ответ, который выдаю обратно:



Вместо HTML страницы уже упростил до простой фразы «hello»…

Кто разбирался с HTTP-протоколом, подскажите плиз, где рыть? Может я ему какие-то данные не досылаю?

Спасибо.

UPD:
Благодаря помощи сообщества победил проблему. Всем откликнувшимся большое человеческое спасибо.

1. Очень много зависит от браузера. FireFox и Chrome более лояльны к ошибкам в принимаемом потоке и редактируют данные на лету, выводя более-менее приемлемый результат. IE8 в этом отношении очень капризный и при малейшей ошибке может просто отказаться отображать страницу.

2. При работе с HTTP крайне внимательно следует относиться к разделению тэгов. Например отсутствие символов начала новой строки перед тегом «Content-Length» приводило к тому что эксплорер не выводил страницу вообще, а остальные браузеры не закрывали соединение по окончании загруки страницы.

3. Минимально-необходимый заголовок включает в себя следующие данные:

HTTP/1.0 200 OK\r\n
Content-Type: text/html; charset=windows-1251;\r\n
Content-Length:475\r\n
Connection: close\r\n
\r\n

где \r\n символы перевода строки и возврата каретки, кодирующиеся в 16-ные 0D 0A

пустая строка после заголовка обязательна.

4. Можно передавать данные несколькими кусками, не повторяя в каждом куске заголовок. Приемлемо работает следующий вариант (с каждой строки новый кусок данных):
— Заголовок с указанием общей длины данных
— данные
— данные
— данные
Я для пробы передаю следующее:

//------------ определение пакетов данных
char TCP_main[] = "HTTP/1.0 200 OK\r\nContent-Type: text/html; charset=windows-1251;\r\nContent-Length:475\r\nConnection: close\r\n\r\n<html><head></head><body><form action='/'method='GET'> IL-2 <input name='ip1' size='1' value='192' maxlength='3'>
<input type='submit' value='Ñîõðàíèòü'></form>\r\n"; //271 165
char TCP_main1[] = "<form action='/'method='GET'> IL-2 <input name='ip1' size='1' value='192' maxlength='3'>
<input type='submit' value='Ñîõðàíèòü'></form>"; //148
char TCP_main2[] = "</body></html>";   //14

//--------------передача

    W5100_Send_TCP_data (TCP_main, 271, 0);
    W5100_Send_TCP_data (TCP_main1, 148, 0);
    W5100_Send_TCP_data (TCP_main1, 148, 0);
    W5100_Send_TCP_data (TCP_main2, 14, 0);



Еще раз спасибо всем откликнувшимся.
  • 0
  • 28 января 2012, 14:37
  • Ultrin

Комментарии (45)

RSS свернуть / развернуть
Если бы еще люди перестали делать из блогов форумы — вообще отлично было бы…
0
  • avatar
  • N1X
  • 28 января 2012, 16:12
На форуме крайне неудобно искать информацию. В блоге она представляется в более удобочитаемом варианте и не надо читать 100500 страниц флуда.
0
Я никогда не отвечу на вопрос в форуме, даже если у меня будет исчерпывающий ответ. Потому, что я не увижу вопроса. У меня нет столько времени, чтобы просмотреть все ветки форума в поисках новых сообщений. Поэтому я туда вообще уже не хожу. Тем более, что персональный блог DI разрешил использовать по своему усмотрению.
+3
Если люди, после подобных разборок напишут некий минимальный How To на основе полученных знаний, увиденных и встреченных проблем и, по возможности, почему именно так, а не иначе и чем иначе чревато, то это было бы здорово :)
+1
Ну вот с такой доработкой это правильно ) согласен
0
«Не выводит» — означает белый экран или продолжающуюся загрузку?
Отладку на уровне браузера пробовали? (для FireFox есть плагин FireBug — в нем раздел «Сеть», для Opera и Chrome тоже есть подобные встроенные инструменты — ищутся в разделе «для разработчиков»)
Первая мысль была в том, что вы не указываете размер отдаваемого контента и не завершаете передачу стоп-байтами
0
идет продолжающаяся загрузка, которая тянется очень долго.
Размер контента попробую указать. А что за стоп-байты? не встречал о них упоминания применительно к HTTP…
0
Нужно или передавать Content-Length, или грохать TCP-соединение на стороне сервера. Еще можно chunked transfer применить, там тоже информация о длине данных внутри потока.
А так у тебя браузер думает, что страничка еще передается.
Но проще всего грохать соединение. Иначе придется разбираться с тем, что браузер затем может попросить новую страничку по тому же соединению.
Мой серверок (виндовый впрочем, не embedded) отсылает Content-Length, Connection: close и грохает соединение.
0
Вот он, правильный ответ.
Человек вам правду говорить по спецификации если вы не указываете хедер Content-Length то браузер ждет пока сервер не закончит передачу обрывом соединения. Хедер Соnnection: close говорит браузеру что это не долговременное соединение обычно если браузер не получает этот хедер то он не закрывает соединение
Если передать оба этих хедера праузер после загрузки полного текста странички (исходя из размера Content lenght) сам грохнет соединение
0
если б оно так работало, я б не создавал темы. Передается и connection:close и content-length и браузер не прибивает соединение. Fire-fox и Хром вообще не обращают на эти теги внимания, а эксплорер хоть и обращает, но соединение сам не прибивает. проверено опытным путем.
0
А разве после HTTP-заголовка не нужно посылать две новых строки (\r\n\r\n, ЕМНИП)?
0
они есть. \r\n транслируются в шестнадцатеричный код 0D 0A
0
А, точно, не заметил.
0
Простите конечно, но дебажить HTTP протокол в 16ричном виде это крайнее извращение
0
Там слева есть колонка, в которой все элементы раскладываются по тэгам. а в 16-ричном удобно смотреть насколько полный идет пакет
0
TCP соединение закрывается? Наверно не хватает заголовков, я бы добавил «Connection: close».
0
  • avatar
  • m0xf
  • 28 января 2012, 16:50
Для отдачи контента по закрытию без указания его размера используется заголовок Transfer-Encoding: chunked.
Connection, вроде бы, немножко для других целей — он указывает браузеру, что делать с TCP соединением (его повторная инициализация и установление занимает время)
0
С этим не всё так просто. Transfer-Encoding: chunked введён только в HTTP 1.1, и при отправке сообщения его нужно делить на блоки и указывать размер каждого блока. Неудобно и избыточно.
Может быть проще будет указать размер в заголовке.
0
Да, соединение надо закрывать:
2.If a Transfer-Encoding header field (section 14.41) is present and
has any value other than «identity», then the transfer-length is
defined by use of the «chunked» transfer-coding (section 3.6),
unless the message is terminated by closing the connection.
из RFC2616
и, как я понял, размер считается по факту — пока данные передаются, принимаем. Про размер блоков ничего не увидел сходу
0
Извиняюсь, привел данные из 1.1, а автор использует 1.0
по факту
7.2.2 Length

When an Entity-Body is included with a message, the length of that
body may be determined in one of two ways. If a Content-Length header
field is present, its value in bytes represents the length of the
Entity-Body. Otherwise, the body length is determined by the closing
of the connection by the server.
Т.е. получается достаточным для ответа закрыть соединение, чтобы браузер понял, что это все.
0
Там три основных варианта
1) По Content-Length (хотя насчет этого не уверен, давно уже читал RFC)
2) По обрыву соединения
3) По завершающему чанку — каждый чанк (блок) предваряется собственным размером:

000D\r\n
Hello, World!\r\n
0007\r\n
(C) Vga

А завершается передачей вместо размера блока маркера конца (точно не помню, вроде таким считается блок нулевой длины).

Есть еще передача со сжатием (Trasfer-Encoding: gzip). Не помню как там определяется конец.
Но если не прибивать соединение — браузер воспользуется им для получения следущей странички, и если сервер не будет этого ожидать — то браузер выдаст ошибку по таймауту.
0
Начал прибивать соединение после передачи пакета. Explorer просто матерится, что страница не может быть получена, а FireBug пишет что во время загрузки соединение было сброшено.
сейчас чтобы было ближе к требуемому пакету, передаю следующую строку:

"HTTP/1.0 200 OK\r\nContent-Type: text/plain; charset=windows-1251;Content-Length:153\r\nConnection: close\r\n\r\n<html><form action='/'method='GET'> IL-2 <input name='ip1' size='1' value='192' maxlength='3'>
<input type='submit' value='Ñîõðàíèòü'></form></html>\r\n";
0
Нельзя просто отправить данные и закрыть соединение — никто не гарантирует, что все будет отправлено. То, что остается в буфере tcp/ip стека на момент вызова сlose(), может быть потеряно. Какого размера буфер в W5100 — понятия не имею, никогда с ним не работал, но как минимум под один пакет должен быть. Правильный метод — вызвать shutdown() и ждать, пока клиент не закроет соединение. shutdown() отправляет FIN пакет после всего, что еще не отправлено. (Как это сделать с W5100 — опять же не знаю).

(Для того, чтобы клиент закрыл соединение в HTTP 1.1, ему надо сообщить длину ответа, либо передать заголовок Connection: close, как уже рассказали выше.)
0
в W5100 TCP-стек аппаратный. Все что я могу ему сказать — это Close. А дальше он сам все что нужно отсылает…
Длинну ответа сообщаю, метод Connection close указываю…
0
А дальше он сам все что нужно отсылает…

В логе сниффера видно, что отсылает?
0
да. наверху есть скриншоты сниффера. там видно, что пакеты идут целиком. внесение изменений целостность пакетов не затронуло.
0
Маленький совет, воспользуйтесь Wireshark в качестве снифера
0
Да, забыл уточнить, что соединение прибивается только после того, как придет прерывание от железки, что даные отправленны.
0
Content-Type: text/plain; charset=windows-1251;Content-Length:
Эм, их же переводами строки разделять надо:
HTTP/1.0 200 OK
Content-Type: text/plain; charset=windows-1251;
Content-Length:153
Connection: close

<html><form action='/'method='GET'> IL-2 <input name='ip1' size='1' value='192' maxlength='3'>
<input type='submit' value='Ñîõðàíèòü'></form></html>

Ну и возможно ты неправильно соединение закрываешь — нужно правильное завершение, как пишет Thorn.
0
добавил переводы строк, страница стала отображаться стабильно и без глюков. Експлорер начал реагировать, но почему-то выводит на экран символ "<" и на этом затыкается. Просмотр страницы в виде html тоже дает только этот символ.
Может конечно у меня сам эксплорер глючит… пока нет возможности перетащить всю рассыпуху на другой комп…
А хром с фоксом работают стабильно.
0
поставил в ответ версию http 1.1 и заработал эксплорер…
0
На данный момент добился следующего:
передаю 2 пакета:

char TCP_main[] = "HTTP/1.0 200 OK\r\nContent-Type: text/html; charset=windows-1251;Content-Length:153\r\nConnection: close\r\n\r\n<html><form action='/'method='GET'> IL-2 <input name='ip1' size='1' value='192' maxlength='3'>
<input type='submit' value='Ñîõðàíèòü'></form></html>\r\n"; //150
char TCP_last[] = "HTTP/1.0 204 OK\r\nContent-Type: text/html; charset=windows-1251;Content-Length:0\r\nConnection: close\r\n\r\n";

После передачи второго пакета глушу соединение.
ФайрФокс и Хром кушают страницу без вопросов, а эксплорер говорит, что не может отобразить страницу… В чем может быть затык?
0
а зачем собственно говоря второй пакет?
Content-Type: text/html; charset=windows-1251;Content-Length:153

это три разных хедера, ставятся через \r\n а не через запятую
0
А charset не к Content-Type относится? Тем более что он через "=" указан, а не ":".
Я, честно говоря, забыл уже. Тем паче сервер так и не допинал (хотел сделать еще чанкед трансфер, прием данных через пост, ну и собсна недоделал главную часть — работающий поверх этого сервера BT-трекер — довел тока до состояния «кое-как работает»).
0
charset то да, относится к Content-Type
а вот Content-Length нет, это отдельный хедер
0
это я уже поправил
0
И было бы неплохо указывать версию браузера
0
Сервера (Server: My superdevice). User-Agent в запросе, а не ответе указывается)
0
да я про версию эксплорера в котором не работает потому как 6/7/8 это разные браузеры
я не про хедеры писал а про то что в IE не работает, а какой конкретно браузер тоже имеет значение
0
Ой, сорри)
0
8 експлорер
0
Спасибо всем кто ответил. сегодня еще поковыряю разные варианты на свежую голову и потом апдейтну главную страницу.
0
Обновил главную страницу порезультатам экспериментов.
0
Точнее, если в каждом пакете слать заголовок, то там должны быть указаны другие поля типа Transfer-Encoding:chunked, но я с ним пока не разбирался.
Заголовок в любом случае передается один раз. Chunked transfer применяется, если размер отдаваемых данных неизвестен (например, они генерятся на лету).
Кроме того, не совсем верно говорить про пакеты применительно к HTTP и TCP. TCP — поточный протокол, и передаваемые данные трактуются как один непрерывный поток. Разбиение на пакеты производит уже сам TCP, чтобы передать их через пакетный протокол IP. Поэтому W5100_Send_TCP_data скорее подобна функции WriteFile в Windows — дописать данные из буфера в поток.
Собственно, я бы хранил всю страничку одним буфером и затем, если у W5100 есть ограничение на кусок передаваемых данных, то использовал бы нечто в духе:
while(Count > 0) // Count - размер передаваемых данных
{
  int BlockSize = max(Count, MaxBlockSize); // MaxBlockSize - максимальный размер блока данных, которые может прожевать за раз W5100
  W5100_Send_TCP_data(Data, BlockSize, 0); // void *Data - указатель на буфер с передаваемыми данными
  Count -= BlockSize;
  Data += BlockSize;
}
0
  • avatar
  • Vga
  • 29 января 2012, 13:53
спасибо за поправку. Поменял «пакеты» на «куски данных» :)
Про страничку одним буффером идея интересная, но мне будет удобней хранить ее именно кусками, т.к. пресеты для форм будут подтягиваться из другого места.
Размер буфера для W5100 задается до 8 Кб на прием и на передачу в зависимости от числа сокетов. Стандартный размер 2 Кб при четырех сокетах.
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.