- Дългото запитване симулира изпращане на данни от сървъра през HTTP, но страда от прекомерни заглавки, особености на времето за изчакване и ограничения на ресурсите.
- Прокси сървърите, ограниченията за връзка на браузъра и кеширането могат незабелязано да прекъснат или влошат поведението при дълги анкети в голям мащаб.
- Протоколи като Bayeux и BOSH се основават на дълго запитване, за да предложат двупосочни съобщения, често заедно със стрийминг.
- Оптимизираните времеви ограничения, пакетирането, компресията и внимателната логика за повторен опит са от съществено значение за поддържане на стабилността на дългите запитвания в JavaScript.
Дългото запитване в JavaScript изглежда измамно просто: дръжте HTTP заявката отворена, докато сървърът има какво да каже, върнете данните и веднага отворете нова заявка. Отвън изглежда почти като комуникация в реално време, така че не е изненадващо, че приложенията за чат, таблата за управление, игрите и системите за известия разчитат на нея от години. Но „под капака“ има фини капани, свързани с производителността, мащабируемостта и надеждността, които се появяват веднага щом приложението ви нарасне отвъд шепа потребители.
Разбирането на реалните проблеми на дългите запитвания в JavaScript означава да се отиде отвъд основното описание „клиентът изпраща заявка, сървърът чака, сървърът отговаря“ и да се разгледат HTTP семантиката, ограниченията на браузъра, прокси сървърите, времето за изчакване и алтернативите като WebSockets, HTTP стрийминг и Server-Sent Events. В това ръководство ще разгледаме подробно колко дълго работи анкетирането, кои проблеми са били идентифицирани от IETF и основните доставчици, както и как да решим кога да го използваме, кога да го оптимизираме и кога да го заменим изцяло.
Какво всъщност представлява дългото гласуване в JavaScript

Казано по-просто, дългото запитване е модел, изграден върху нормалния HTTP, при който браузърът изпраща заявка, а сървърът умишлено забавя отговора, докато не станат налични нови данни или не изтече време за изчакване. Веднага щом браузърът получи отговор, той обработва данните и веднага изпраща друга заявка, като поддържа връзката „почти винаги“ в режим на чакане.
Този поток е много различен от класическото кратко запитване, при което клиентът „засича“ сървъра през фиксиран интервал от време, питайки „нещо ново?“, независимо дали има актуализации. При дълго запитване, сървърът държи заявката отворена по време на периоди на неактивност, така че клиентът не е необходимо да изпраща многократно празни запитвания, когато нищо не се е променило.
Типичният дълъг цикъл на запитване в JavaScript изглежда като рекурсивна функция за абонамент, която извиква fetch (или XMLHttpRequest), чака отговора, обработва полезния товар и след това отново се извиква. Ако мрежата прекъсне връзката или възникне грешка, кодът прави повторен опит незабавно или след кратко забавяне, опитвайки се да поддържа тази чакаща връзка активна колкото е възможно повече.
Този подход работи особено добре, когато съобщенията са сравнително редки, защото времето на празен ход на връзката не генерира мрежов трафик или натоварване на процесора, надхвърлящо разходите за поддържане на отворен сокет. Потребителят получава почти мигновени актуализации, докато сървърът избягва да бъде бомбардиран с постоянни проверки чрез анкетиране.
Въпреки това, веднага щом събитията станат чести, всяко събитие има тенденция да задейства цял цикъл на HTTP отговор – ред за състоянието, заглавки, удостоверяване, тяло – така че цената на съобщение може да се увеличи драстично и моделът на трафика започва да изглежда като пикове на заявки/отговори, назъбени като трион. Именно тук започват да се появяват проблеми с мащабирането и режийните разходи в реалните приложения.
Кратко анкетиране срещу дълго анкетиране срещу стрийминг

За да разберем наистина проблемното пространство, е полезно да сравним дългото запитване с краткото запитване и HTTP стрийминга, тъй като и трите са начини за фалшиво „прокарване“ през фундаментално протокол за заявка/отговор като HTTP.
Краткото запитване е наивният вариант: клиентът периодично изпраща HTTP заявки (например на всеки 5-10 секунди) и сървърът незабавно отговаря с всички съобщения, пристигнали след последното запитване. Ако няма нищо налично, сървърът все пак отговаря, често с празен полезен товар, а клиентът просто чака до следващия интервал.
Този модел има два очевидни недостатъка: латентността на съобщенията може да бъде толкова висока, колкото интервалът на запитване, а сървърът е принуден да обработва повтарящи се заявки, дори когато няма нови данни. За малки или нискотрафикови системи това може да е приемливо, но в голям мащаб това хаби процесорни цикли, мрежова честотна лента и може да доведе до забележими забавяния за потребителите.
Дългото запитване подобрява това, като позволява на сървъра да „задържи отворена“ всяка заявка, докато се случи нещо интересно – събитие за доставка или праг на изчакване. Латентността за нови съобщения вече е близка до едно двупосочно мрежово пътуване, а празни отговори се получават много по-рядко, което намалява загубата на трафик.
HTTP стриймингът отива още една стъпка, като никога не затваря отговора: сървърът изпраща един HTTP отговор и след това предава множество блокове данни през една и съща връзка, разделени от някакво рамкиране на ниво приложение. Клиентът чете потока постепенно, вместо да чака завършването на целия отговор.
В HTTP/1.1 това обикновено се реализира чрез кодиране на блокове за трансфер, където сървърът задава Transfer-Encoding: chunked и след това изпраща всяка част от данните като отделен блок със собствен заглавен файл с дължина. В настройките в стил HTTP/1.0, стриймингът може да се постигне и чрез пропускане на Content-Length и използване на connection close като маркер за край на потока.
Ключовата разлика е, че дългото запитване затваря отговора след всяко съобщение, принуждавайки нова заявка за следващото събитие, докато стриймингът поддържа същия отговор активен и изпраща съобщенията, когато се появят. Това има сериозни последици за латентността, използването на памет, прокси сървърите и начина, по който рамкирате съобщенията на приложното ниво.
Как работи HTTP дългото запитване „под капака“
От гледна точка на HTTP протокола, дългото запитване не въвежда никакви нови методи или кодове за състояние; то просто разширява обичайната семантика на заявка, която чака отговор. Анализът на IETF на „двупосочния HTTP“ ясно показва, че дългите запитвания все още са валидни за HTTP 1.0/1.1, но доближават модела до неговите граници.
Основният жизнен цикъл на дълго взаимодействие с анкета изглежда така: клиентът изпраща първоначална заявка и я паузира, сървърът отлага отговора, докато се случи събитие, промяна на състоянието или изтичане на времето за изчакване, след което сървърът връща пълен HTTP отговор (често 200 OK) с данните за събитието и накрая клиентът бързо издава нова заявка.
Този модел може да работи както през постоянни, така и през непостоянни HTTP връзки; с постоянни връзки избягвате натоварването от многократни TCP ръкостискания, като използвате повторно един и същ сокет за множество дълги запитвания. На практика, поддържането на една и съща TCP връзка активна е почти винаги за предпочитане от гледна точка на производителността.
Сървърите, които внедряват дълго запитване, обикновено трябва да управляват два ресурсни контейнера на клиент: самата TCP връзка и чакащата HTTP заявка в някаква опашка или цикъл на събития. Операционните системи обикновено могат да обработват ефективно голям брой отворени сокети, но някои HTTP сървъри или шлюзове заделят значителен обем памет за всяка заявка, което може да се превърне в истинско пречка.
Едно интересно свойство на дългото запитване е, че при голямо натоварване то е склонно да се влошава плавно чрез увеличаване на латентността, вместо да се повреди рязко. Ако сървърът е бавен, съобщенията, предназначени за клиент, ще се наредят на опашка, докато не може да бъде изпратен отговор; множество събития в опашката могат дори да бъдат групирани в един дълъг отговор на анкета, което между другото намалява натоварването на всяко съобщение.
Проблеми и ограничения на дългосрочните анкети
Въпреки че е широко използван и стандартизиран на практика, дългите анкети въвеждат няколко добре познати технически проблема, които лесно могат да бъдат засегнати от JavaScript, ако не сте внимателни. Тези проблеми се проявяват в използването на честотна лента, латентността, управлението на връзките и поведението на посредници като прокси и кеш сървъри.
Разходите за заглавки са първият разход, с който се сблъсквате: всяка дълга заявка и отговор на анкета е пълно HTTP съобщение, обикновено с бисквитки, заглавки за оторизация и други метаданни, които биха могли да засенчат действителния полезен товар. За малки, рядко изпращани съобщения това натоварване може да е приемливо, но в сценарии за таксуване, базирано на обем, или мрежи с ограничена честотна лента, разликата между размера на заглавката и размера на полезния товар може да стане скъпа.
Максималната латентност е друг фин проблем: докато средната латентност при дълго запитване за нови събития е приблизително един мрежов транзит, забавянето в най-лошия случай може да бъде повече от три транзита. Ако съобщение пристигне веднага след като сървърът е изпратил отговор, сървърът трябва да изчака следващата заявка от клиента, преди да може да достави това съобщение, а загубата на TCP пакети или повторното предаване могат да удължат това време допълнително.
Установяването на връзка често се повдига като проблем, особено когато хората сравняват дългите анкети с WebSockets. Ако всеки дълъг отговор на запитване водеше до затваряне на HTTP връзката (и подлежащата TCP връзка), цената на многократното отваряне на сокети би била огромна. За щастие, дългите запитвания могат и трябва да се наслагват върху постоянните връзки, така че кратката пауза между отговорите да не се интерпретира като бездействие; това позволява на транспорта да остане отворен и да може да се използва многократно.
Разпределението на ресурси на сървъра и прокси сървъра е основно практическо ограничение: всяка неизпълнена заявка консумира памет и евентуално нишка или работник в синхронни архитектури. Много по-стари или блокиращи сървъри просто не могат да се мащабират до десетки хиляди едновременни дълги запитвания, защото техният модел на паралелизъм очаква всяка заявка да завърши бързо; за тези системи асинхронният входно-изходен процес или дизайнът, управляван от събития, е почти задължителен.
Кеширането може също да прекъсне дългото запитване, ако не е изрично потиснато. Междинните кешове могат да решат да използват повторно или да съхраняват отговори, освен ако не маркирате ясно дългите заявки и отговори на анкети с Cache-Control: no-cache (и свързаните с тях заглавки), като по този начин гарантирате, че всяка заявка действително достига до оригиналния сървър и не се предоставя с остарели данни.
Временни ограничения, прокси сървъри и поведение на посредниците
Един от най-досадните проблеми в реалния свят с дългите запитвания в JavaScript е, че не контролирате напълно мрежата между браузъра и сървъра. Прокси сървъри, шлюзове, балансьори на натоварването и дори настройките по подразбиране на браузъра налагат таймаути или стратегии за буфериране, които могат да разрушат илюзията за активна връзка.
Дългите заявки за анкети би трябвало да останат отворени до събитие или време, което вие контролирате, но в действителност много посредници ще прекъснат връзките след по-кратък, фиксиран период. Въпреки че браузърите могат да позволяват до около 300 секунди по подразбиране, някои прокси сървъри налагат много по-малки таймаути, което означава, че дългата ви анкета ще завърши с HTTP 504 Gateway Timeout или просто с нулиране, принуждавайки клиента да се свързва отново по-често, отколкото сте възнамерявали.
Експериментите и оперативният опит показват, че времето за изчакване около 30 секунди е по-безопасен компромис в много среди, като 120 секунди често работят, но са по-крехки. Доставчиците на мрежово оборудване, които искат да бъдат удобни за дългите запитвания, се насърчават да използват значително по-дълги времена за изчакване от типичните времена за преминаване през средна мрежа, така че легитимните дълги запитвания да не бъдат прекратявани преждевременно.
Обратните прокси сървъри и обединяването на връзки въвеждат друг клас проблеми. Някои прокси конфигурации споделят малък пул от връзки към сървъра между много клиенти; ако дългите заявки заемат тези споделени връзки за продължителни периоди, други заявки могат да бъдат блокирани или поставени на опашка след тях, което подкопава производителността на целия сайт.
При HTTP стрийминг, междинното буфериране е още по-голям риск, тъй като прокси сървърите могат да буферират частични отговори и да препращат данни само след като са натрупали пълен отговор. В този случай, стрийминг сървър, който изпраща малки JavaScript парчета, очакващи незабавно изпълнение в браузъра, може да открие, че посредник държи всичко, докато връзката приключи, което напълно унищожава поведението в реално време.
Ограничения на браузъра и брой връзки
От JavaScript никога не манипулирате директно TCP връзки; виждате само конструкции от високо ниво като fetch или XMLHttpRequest, но браузърите налагат ограничения за това колко паралелни връзки могат да бъдат отворени към един и същ хост. В исторически план това ограничение е било две връзки на произход, което е правело техниките в стил Комет особено трудни.
Съвременните браузъри са облекчили тези ограничения до около шест или осем връзки на хост, но все още няма стандартен начин от JavaScript да се попита „колко връзки са останали?“ или да се координира използването между раздели и iframe-ове. Всеки раздел може самостоятелно да стартира собствена дълга анкета, бързо изчерпвайки наличните слотове и блокирайки други важни заявки като CSS, изображения или API извиквания.
Най-добра практика от страна на сървъра е да се използват „бисквитки“ или някакъв механизъм за корелация, за да се откриват множество дълги анкети, идващи от един и същ браузър, и да се избегне отлагането на всички тях. Като отговаряте бързо на изключително дълги анкети и наистина задържате само една или малък брой, можете да намалите риска от гладуване на връзката или смущения в конвейера.
Някои протоколи от по-високо ниво, изградени върху дълги запитвания, като Bayeux и BOSH, изрично проектират около ограниченията за връзка с браузъра. Те често използват най-много две неизпълнени HTTP заявки едновременно и избягват да разчитат на HTTP pipeline, който е слабо поддържан и не може да се контролира от JavaScript.
HTTP конвейеризация, рамкиране и подреждане на съобщения
Въпреки че HTTP конвейеризацията (изпращане на множество заявки една след друга през една и съща връзка преди получаване на отговори) на теория би могла да намали дългата латентност при запитване, на практика тя е крехка и непоследователно имплементирана. RFC 2616 е предпазлив по отношение на конвейеризацията, особено за POST заявки, а посредниците или клиентите могат да я деактивират напълно.
Протоколите, които се опитват да използват конвейерно обработване за дълги запитвания, първо трябва да открият, че конвейерното обработване е надеждно поддържано от край до край; ако не е, те се връщат към консервативно, неконвейерно поведение. От JavaScript средата на браузъра нямате куките, за да управлявате това, така че повечето JavaScript-базирани стекове за дълго запитване просто приемат, че конвейерната обработка не е налична.
Рамкирането – начинът, по който разделяте непрекъснат поток от байтове на отделни съобщения на приложението – е друга фина разлика между дългото запитване и HTTP стрийминга. При дългото запитване, всеки HTTP отговор естествено носи точно едно или няколко съобщения, така че рамкирането е имплицитно: един отговор е равен на един блок смислени данни.
При стрийминг не можете да разчитате на границите на HTTP фрагментите като рамка на приложението си, защото прокси сървърите могат да преразпределят потока от данни, да обединяват фрагменти или да ги разделят по различен начин. Това означава, че приложението трябва да вгради свои собствени разделители или префикси за дължина в полезния товар и да го анализира съответно на клиента.
Подреждането и надеждността на съобщенията не са гарантирани от самото дълго запитване; те зависят от протокола на приложението и слоя за съхранение. Ако клиент прекъсне връзката и се свърже отново по средата на потока, са ви необходими изрични механизми (като поредни номера или идентификатори на последното събитие), за да се гарантира, че съобщенията няма да бъдат загубени или доставени нередно.
Съображения за сигурност при дългите анкети в JavaScript
Дългото запитване като техника не променя основния модел за сигурност на HTTP, но начинът, по който често се имплементира – особено в браузъри, използващи JavaScript – може да отвори вратата за допълнителни рискове.
Много решения за междудомейно дълго анкетиране разчитат на изпълнението на JavaScript, върнат от сървъра, понякога чрез обратни извиквания в стил JSONP или друго динамично инжектиране на скриптове. Ако вашият сървър е уязвим за атаки с инжектиране, нападателят може потенциално да вмъкне произволен код в тези отговори, които браузърът след това изпълнява с привилегиите на страницата.
Използването на HTTPS навсякъде е неоспоримо: криптирането на транспорта предпазва от манипулиране от страна на човек по средата и подслушване на дълготрайни връзки. В комбинация със стабилно удостоверяване и оторизация (например, оторизация, базирана на токени, и контрол на достъпа, базиран на роли), можете да ограничите крайните точки за дълго запитване до целевите клиенти.
Тъй като дългите анкети поддържат много връзки отворени за дълги периоди, това може да направи DoS атаките по-привлекателни: нападателят може да се опита да изчерпи сървърните ресурси, като отвори много фалшиви дълги анкети. Ограничаването на скоростта, квотите за връзка на IP адрес или токен и разумните таймаути са основни защитни мерки.
Специфичните за браузъра заплахи, като CSRF, обикновено са по-малко важни за чисти XHR/fetch дълги анкети, които не разчитат на околни бисквитки, но ако са включени бисквитки, все пак трябва да третирате тези крайни точки както всеки друг чувствителен API. „Бисквитките“ на SameSite, CSRF токените, където е уместно, и строгите CORS политики са част от засилена система.
Дълго запитване срещу WebSockets и събития, изпратени от сървъра
От гледна точка на JavaScript разработчика, дългите ползи, WebSockets и Server-Sent Events са все начини за постигане на функции за „чувстване в реално време“, но компромисите се различават доста.
WebSockets установяват истински постоянен, пълнодуплексен канал между браузъра и сървъра. След първоначалното HTTP ръкостискане за надграждане, кадрите с данни могат да се предават в двете посоки без допълнителните HTTP заглавки за всяко съобщение, което минимизира латентността и използването на честотна лента за всяко съобщение.
Това прави WebSockets идеални за сценарии с висока честота, като например мултиплейър игри, съвместно редактиране или телеметрични потоци, където съобщенията са малки, но чести. Недостатъкът е, че някои корпоративни защитни стени, стари прокси сървъри или наследени клиенти все още не работят добре с надстройките на WebSocket, а сървърите трябва да използват различен модел на програмиране и инфраструктура, настроена за голям брой дълготрайни сокети.
SSE е по-прост от WebSockets, когато се нуждаете само от push сървър към клиент, но не може да изпраща съобщения от браузъра обратно по същия канал; все още разчитате на нормални HTTP заявки за комуникация клиент-сървър. Това също така изисква посредниците да толерират стрийминг и да не буферират прекомерно частични отговори.
В сравнение с тях, дългото запитване често е „най-малкият общ знаменател“, който работи в повече среди, включително по-стари браузъри и консервативни мрежови настройки. Ето защо много платформи в реално време исторически са използвали дългото запитване като резервен вариант, когато WebSockets са били блокирани, като постепенно са надграждали връзките, когато възможностите са позволявали.
Протоколи от реалния свят, използващи дълги ползи: Bayeux, BOSH, SSE-подобни API
Няколко протокола от по-високо ниво са изградени върху дългото запитване и стрийминг, за да скрият сложността от разработчиците на приложения и да осигурят последователен API. Разбирането на начина, по който използват дългите анкети, помага да се изяснят както силните, така и слабите страни на техниката.
Протоколът Bayeux, популяризиран от проекта CometD, поддържа както HTTP дълги запитвания, така и стрийминг като опции за транспорт. Клиентът на Bayeux обикновено използва две HTTP връзки към сървър, така че съобщенията да могат да се движат асинхронно в двете посоки, без да бъдат блокирани след дълги заявки за анкети.
По време на първоначалното ръкостискане, клиентът и сървърът договарят кои двупосочни техники се поддържат – дълго запитване, стрийминг и т.н. – и клиентът избира една от припокриващите се. За JavaScript клиенти, Bayeux обикновено избягва зависимостта от HTTP pipeline и вместо това се ограничава до две неизпълнени заявки, за да избегне проблеми с ограничението на връзката на браузъра.
BOSH (Bidirectional-streams Over Synchronous HTTP) идва от света на XMPP и е проектиран да емулира TCP-подобна сесия през HTTP, използвайки дълго запитване. BOSH мениджърът на връзки държи заявката на клиента отворена и отговаря само когато има данни от сървъра на приложенията; веднага щом клиентът получи този отговор, той изпраща нова заявка, като почти през цялото време поддържа поне една чакаща дълга анкета.
BOSH може да използва една или две паралелни HTTP двойки заявка-отговор, договорени в началото на сесията, и внимателно ги управлява, за да спазва ограниченията на връзката на браузъра, като същевременно позволява асинхронно двупосочно изпращане на съобщения. Това забранява кодирането на фрагментирани трансфери, за да се избегнат проблеми с буферирането в посредниците, и позволява компресия чрез Content-Encoding за по-ефективно използване.
Събитията, изпратени от сървъра, макар и обикновено да се реализират чрез стрийминг, а не чрез дълго запитване, са тясно свързани по дух. Спецификацията на W3C описва използването на текстови/събитийно-потокови отговори и предлага деактивиране на HTTP chunking, освен ако честотата на събитията не е достатъчно висока, отново за да се избегнат определени проблеми с буферирането и междинните процеси, наблюдавани при стрийминг през HTTP/1.1.
Оптимизиране и засилване на дългото запитване в JavaScript приложения
Ако решите, че дългото запитване е правилният избор или необходима резерва за вашето JavaScript приложение, има няколко стратегии, които да го направят по-ефективно, мащабируемо и стабилно.
Първо, внимателно настройте времето си за изчакване. Твърде кратко време ще ви накара да хабите ресурси за обработка на чести повторни връзки и рискувате тълпи от клиенти, които се свързват отново едновременно; твърде дълго време ще увеличите шанса за достигане на ограниченията на прокси сървъра или балансьора на натоварването, причинявайки мистериозни прекъсвания. На практика стойностите в диапазона от 20-30 секунди за WaitTimeSeconds (в API като Amazon SQS) и подобни времеви ограничения на ниво приложение често постигат добър баланс.
След това, помислете за групиране на събития от страна на сървъра, когато множество съобщения са поставени на опашка за клиент. Предоставянето на няколко актуализации в един дълъг отговор на анкета драстично намалява натоварването на всяко съобщение и може да помогне на системата да се деградира по-плавно под натоварване, като замени латентността с пропускателна способност.
Компресията е друга лесна победа: активирането на gzip или подобно кодиране на съдържание за JSON полезни товари при дълги запитвания може да намали използването на честотна лента, особено когато съобщенията споделят повтаряща се структура. Компромисът е допълнителна цена на процесора за компресия, но в много реални внедрявания това се компенсира от намаленото време за мрежов трансфер.
От страна на JavaScript, надеждната обработка на грешки и логиката за повторен опит са неоспорими. Вашият цикъл на абонамент трябва да открива мрежови грешки, таймаути или деформирани отговори и да прави повторен опит с отсрочка, вместо да претоварва сървъра. Експоненциалното отсрочка с трептене е често срещан модел за предотвратяване на синхронизирани бури от повторни опити по време на прекъсвания.
И накрая, бъдете дисциплинирани по отношение на почистването на връзките, когато компонентите се демонтират или разделите се затварят. Зомби циклите на анкетиране, които продължават да се изпълняват във фонов режим, могат да изразходват както клиентските ресурси, така и капацитета на сървъра, така че винаги се уверявайте, че имате механизъм за отмяна на неизпълнени извличания или прекратяване на контролери, когато даден изглед бъде унищожен или потребителят напусне системата.
Дългото запитване в JavaScript остава мощен инструмент за изграждане на функции, работещи почти в реално време, в среди, където WebSockets или SSE не са налични, но то идва с редица скрити разходи, свързани със заглавки, времеви ограничения, прокси сървъри и използване на ресурси, които трябва да разберете и управлявате, ако искате приложението ви да се мащабира гладко.