LINUX.ORG.RU

Может ли HTTP сервер достоверно убедиться, что клиент полностью получил содержимое ответа на запрос

 , , ,


1

1

... без дополнительных подтверждений на уровне протокола приложения?

Интуитивно кажется, что нет, но хотелось бы формальных пруфов.

Есть некая железка, которая качает обновления фирмвари. И есть сервер, с апачем и похапе, который эту фирмварь из базы читает и отдаёт. Заказчики хотят отметить в базе железку обновлённой по факту успешного скачивания, я предлагаю добавить дополнительный запрос от железки с подтверждением успешной установки.

Факты таковы:

1) PHP скрипт по дефолту прибивается, если клиент отрубился в процессе и сервер это обнаружил

2) Но это поведение можно поменять

3) Сервер не всегда может обнаружить факт преждевременного отрубания клиента. Допустим, соединение остаётся активным после обработки запроса

4) Апач чего-то там пишет в логах, включая количество отправленных байт. Можно ли принимать их во внимание?

Допустим, я упоролся и написал кастомный вебсервер на сишечке исключительно с целью гарантированного подтверждения успешного скачивания, и тщательно обрабатываю события с сокетом, после отправки фирмвари делаю shutdown() и close() и проверяю возвращаемые значения. Даёт ли корректное завершение соединения с клиентом (т.е. shutdown() и потом close() на дескрипторе сокета вернули 0) гарантию доставки? Т.е. что стороны успешно обменялись всеми FIN-ACK пакетами и доставили недоставленное при необходимости, со всеми нужными подтверждениями?

★★★★★

Надо дождаться FIN с той стороны. Это будет не 100% гарантия правда, есть ещё вариант, что там просто закрыли соединение в процессе приёма данных. shutdown насколько я знаю - не нужен для tcp сокетов.

pon4ik ★★★★★ ()

После закачки клиент делает GET запрос к серверу вида http://ipserver/ipclient/hash и всё. Ну или post по той же аналогии.

anonymous ()

Так, погоди, а зачем эти все извращения? У нас есть кастомный вебсервер на сишечке как и у тебя, почему у тебя в ответ не летит 200\403? Получил весь пейлоад (Content-Length/Chunked) - 200. Не получил - 403. Если еще X-Checksum-SHA1 в заголовок прикрутишь - вообще будешь большим молодцом.

PPP328 ★★★★ ()
Ответ на: комментарий от PPP328

очему у тебя в ответ не летит 200\403? Получил весь пейлоад (Content-Length/Chunked) - 200.

код HTTP ответа до пейлоада отправляется, не?

Harald ★★★★★ ()

безотносительно HTTP, «отметить в базе железку обновлённой по факту успешного скачивания» — отличный способ огрести очень больших проблем

colok ()
Ответ на: комментарий от Harald

Ох, прикол, портят нас всё-таки всякие бусты и кьюты. Пока читал man нашёл такую вот опцию - SO_LINGER, вопрос только что подразумевается под успешной отправкой, положить в устройство или получить ACK’и.

pon4ik ★★★★★ ()

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

Скачать и установить — разные действия, между ними с твоей железкой много чего может случиться. Лучше делать явный запрос с подтверждением, чем думать о http.

И вообще, если у железки есть теоретическая возможность откатиться на старую версию прошивки, то, может, проще говорить версию серверу в каждой сессии или что там у тебя вместо них?

x3al ★★★★★ ()

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

pfg ★★★★★ ()
Последнее исправление: pfg (всего исправлений: 1)

Есть некая железка, которая качает обновления фирмвари

Заказчики хотят отметить в базе железку обновлённой по факту успешного скачивания

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

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

Ты прав. И факт скачивания ничего не говорит о состоянии железки после того как.

Если после скачивания происходит какое-то дополнительное шаманство типа прошивания флешки или чего-то еще, то ни в коем случае не надо полагаться на какие-либо стандартные ответы веб-серверов (я отправил и они там точно получили), а надо отдавать прошиву девайсу (а твой коннект открыт), девайс что-то делает (а твой коннект открыт), прошивка прошивается (а твой коннект открыт), девайс прошился и проверил себя, отправил конфирмейшен, закрывайся. Т.е. это школьная эмбедо-задача по «обновлению с гарантией».

И если ты готов чуть-ли не свой вебсервер пилить ради такого, то уж простейшую обёртку для «взять прошиву, отдать прошиву, дождаться ответа» всяко сообразишь. Другое дело - что происходит с железякой при обновлении, сколько это длится ну и вообще.

Brillenschlange ()

Я просто прикручиваю хеш - клиент забирает вначале хеш, который посчитал сервер, потом данные, потом отправляет свой хеш - оба два в курсе что все прошло успешно, но в 99% хватает только проверки на клиенте (ибо один фиг он должен перезапросить если хеш не сойдётся)

rukez ★★★ ()

Заказчики хотят отметить в базе железку обновлённой по факту успешного скачивания

А разгребать факап если прошивка не установилась будешь ты или заказчики? Если ты то я бы посоветовал отговорить заказчиков.

ya-betmen ★★★★★ ()
Последнее исправление: ya-betmen (всего исправлений: 1)
Ответ на: комментарий от Harald

тебе уже несколько раз сказали как правильно, отмечать железку обновленной можно минимум когда она сама подтвердила правильность хэша. но ты всё пытаешься найти оправдание своей лени и сделать-таки через жопу

colok ()
Ответ на: комментарий от Harald

Если правильно делать то нужно чтобы в момент коннекта к сети железка отсылала свою версию прошивки.

ya-betmen ★★★★★ ()
Ответ на: комментарий от Harald

пруфом невозможности именно в рамках TCP? но зачем?

на уровне сокета FIN+ACK может тебе подтвердить, что клиент получил данные, окей. но это никак не гарантирует даже того, что sync этого говна на fs пройдет успешно, не говоря уже о том, что нет никакой уверенности в том, что соседский Вася не заменил твой апдейт на 4 гигабайта цэпэ (тут, конечно, кроме контрольной суммы желательно иметь еще и ключи, но что есть)

colok ()

я предлагаю добавить дополнительный запрос от железки с подтверждением успешной установки

Так и делай.

theNamelessOne ★★★★★ ()

отдать клиентом «299 Downloaded and Installed» по факту скачки и установки?

anonymous ()
Ответ на: комментарий от Harald

Сотни реализаций вполне себе живут на stm и чот не плачут.

На крайний случай есть reverse connection

PPP328 ★★★★ ()

лол OTA существует с начала времен

и вдруг приходит какой то кекс и начинает ерничать

жаль ко где такую школоту понабирают

anonymous ()

Т.е. что стороны успешно обменялись всеми FIN-ACK пакетами и доставили недоставленное при необходимости, со всеми нужными подтверждениями?

Нет. Вы успешно поставили задание в буфер TCP после находящихся там еще не отправленных данных. Больше вы ничего не знаете.

level1 ()
Ответ на: комментарий от Harald

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

level1 ()
Ответ на: комментарий от level1

Базово отправка ответа сервером выглядит так:

write()
write()
....
write()
close() // здесь предполагаем отсутствие keep-alive, http2 и вот этого всего
Если все эти вызовы(в том числе close()) завершены успешно, то можно говорить, что хост на той стороне сокета получил весь ответ. Получило ли весь ответ приложение-клиент нельзя сказать. Точно так же нельзя сказать, что устройство успешно обновлено(оно могло банально не загрузиться после перепрошивки)

cobold ★★★★ ()
Ответ на: комментарий от cobold

Если все эти вызовы(в том числе close()) завершены успешно, то можно говорить, что хост на той стороне сокета получил весь ответ.

Это полная чушь.

Напишите простенький сервер без потоков и форков, который бы отдавал 10 байт за один write. И потом сразу же close. Поставьте отладку после write и close.

Потом напишите такой же простой клиент, который бы читал 5 байт, засыпал на 10 секунд, и потом читал оставшиеся 5 байт.

Наслаждайтесь.

level1 ()

Клиент после того, как прочитал ответ, закрывает свою половину соединения. Для этого он посылает TCP пакет FIN. Сервер может использовать этот факт, как индикацию того, что клиент успешно принял ответ сервера. Если такой пакет в течение определённого времени не пришёл или пришёл TCP пакет RST, значит клиент не принял ответ сервера по какой-либо причине. Конечно отсчёт нужно начинать после того, как сервер отошлёт последнюю порцию данных.

Чисто теоретически могут быть упоротые роутеры между вами, которые могут решить закрыть соединение за клиента. Поэтому такой способ нельзя назвать 100% надёжным, особенно при использовании HTTPS. Правильный способ это отдельный запрос. Но если никак не выходит сделать по-нормальному, можно и как описано выше.

Также нужно отметить, что последние версии HTTP используют UDP и тут всё будет ещё сложней.

В общем не советую так делать. Не нужно лезть на уровень протокола ниже, надо оставаться на уровне HTTP. А на этом уровне нужно отсылать отдельное подтверждение отдельным запросом.

Legioner ★★★★★ ()
Последнее исправление: Legioner (всего исправлений: 2)
Ответ на: комментарий от Legioner

Конечно отсчёт нужно начинать после того, как сервер отошлёт последнюю порцию данных.

Сервер записал последнюю порцию данных. Она встала в буфер его сокета, но еще не отправлена. В этот момент мышка бежала, хвостиком махнула, приложение клиента останавливается, тцп клиента посылает фин серверу. Сервер думает, что всё ок.

level1 ()
Последнее исправление: level1 (всего исправлений: 1)
Ответ на: комментарий от level1

ок, я залез в исходники ядра,

net/ipv4/tcp.c

реализация

/*
 *      Shutdown the sending side of a connection. Much like close except
 *      that we don't receive shut down or sock_set_flag(sk, SOCK_DEAD).
 */

void tcp_shutdown(struct sock *sk, int how)
{
        /*      We need to grab some memory, and put together a FIN,
         *      and then put it into the queue to be sent.
         *              Tim MacKenzie(tym@dibbler.cs.monash.edu.au) 4 Dec '92.
         */
        if (!(how & SEND_SHUTDOWN))
                return;

        /* If we've already sent a FIN, or it's a closed state, skip this. */
        if ((1 << sk->sk_state) &
            (TCPF_ESTABLISHED | TCPF_SYN_SENT |
             TCPF_SYN_RECV | TCPF_CLOSE_WAIT)) {
                /* Clear out any half completed packets.  FIN if needed. */
                if (tcp_close_state(sk))
                        tcp_send_fin(sk);
        }
}

вроде бы как бы да, но тут не совсем очевидно, вдруг вызывающий реализацию shutdown код блокирует вызов и ждёт ответа с той стороны :)

Harald ★★★★★ ()
Ответ на: комментарий от Harald

Ага, и ждет 2MSL на таймвейт. Хоть раз такое где-нибудь встречали?

level1 ()

хотелось бы формальных пруфов.

А в чем именно надо убедиться-то — что получил или что установил?

Nervous ★★★★★ ()
Ответ на: комментарий от anonymous

Да, именно так и будет, придет фин, если мягко заглушили машину или приложение.

level1 ()
Последнее исправление: level1 (всего исправлений: 1)
Ответ на: комментарий от level1

Читайте внимательно. В вашем примере речь о клиенте-приложении, а в том на что вы отвечаете речь о хосте

cobold ★★★★ ()
Ответ на: комментарий от cobold

Даже если речь о хосте, то вы все равно не правы. Напишите сервер, который пишет сначала 5 байт, потом засыпает на 10 секунд, пишет оставшиеся 5 байт, и закрывает сокет. Клиент в цикле читает в большой буфер и печатает в стдерр. Теперь запустите клиент. Запустите сервер. Когда сервер отправит первую порцию, выдерните патчкорд, у вас на это есть 10 секунд. Сервер успешно вернется из close. На хосте клиента последних 5 байтов нет.

Но дело в том, что вы морочите тс голову. Ему безразлично что творится в тцп, если только вместо того, чтобы послать дополнительный запрос на уровне приложения вы не предлагаете писать модуль ядра, который бы лез в кишки тцп, потому что другого способа добраться до «данных на хосте» у него нет.

level1 ()

отметить в базе железку обновлённой по факту успешного скачивания

  • это очень неправильное решение с кучей граблей.

Лучше придумывать как детектрировать работоспособность и версию ПО железки после обновления.

Если фирмварь своя - то самое простое окончательное решение это добавить атчод железки серваку после первой удачной загрузки после обновления.

Stanson ★★★★★ ()

пздц… железка окирпичится в процессе обновления и усё

нормальная практика - опрос или пуш версии прошивки в начале сеанса обмена с устройством: я устройство 123, железо 0.1, прошивка 0.3 и погнали.

zudwa ()

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

Т.е. нет, ты не можешь сделать то, что ты хочешь и не надо, потому что тебе всё равно полезно сделать больше.

max_lapshin ★★★ ()
Для того чтобы оставить комментарий войдите или зарегистрируйтесь.