Все по­мнят мой гнев­ный (ско­рее, иро­нич­ный вен­ти­ля­тор­но-ло­па­точ­ный вброс, на ко­то­рый мно­гие ку­пи­лись, смот­ри UPD вни­зу сле­ду­ю­щей ссыл­ки) пост про HTML5 и про весь тот шум, ко­то­рый на­гне­та­ет­ся во­круг сы­рой и не­го­то­вой к массо­во­му ис­поль­зо­ва­нию тех­но­ло­гии. В ка­кой-то ме­ре, дан­ный пост ста­нет оче­ред­ным то­му до­ка­за­тель­ством, но, в то же вре­мя, если этот эфе­мер­ный на­бор стан­дартов бу­дет когда-то окон­чен, то мы, ве­браз­ра­бот­чи­ки, по­лу­чим от­лич­ный инстру­мен­та­рий, ко­то­рый ли­шит необ­хо­ди­мо­сти со­би­рать ве­ло­си­пе­ды с квад­рат­ны­ми ко­ле­са­ми для ез­ды по стек­лян­ным рель­сам. Ну вы по­ня­ли.

Да­вайте по по­ряд­ку. Сей­час в мо­де real-time ком­му­ни­ка­ции, в тех­ни­че­ском смыс­ле это­го сло­ва. Кон­такт, лицокни­га, гугло­плю­сы - все пере­чис­лен­ные со­ци­аль­ные се­ти имею свой WebIM, поз­во­ля­ю­щий в ре­аль­ном вре­ме­ни об­щать­ся со свои­ми дру­зья­ми и подру­га­ми. Прав­да, кон­цеп­ция там немного дру­гая, ча­ты этих со­ци­а­лок, как пра­ви­ло, пред­став­ляют со­бой JavaScript оберт­ку во­круг XMPP про­то­ко­ла, но смысл по­ня­тен. Еще, спра­ведли­во­сти ра­ди, сто­ит за­ме­тить, что WebSockets и Server-Sent Events в на­сто­я­щее вре­мя вы­де­ле­ны из спе­ци­фи­ка­ции HTML5 в отдель­ные до­ки, но, опять та­ки, я утвер­ждаю и бу­ду утвер­ждать, что HTML5 - не тех­но­ло­гия, а мар­ке­тин­го­вый бренд и пись­ко­ме­рял­ка совре­мен­ных брау­зе­ров, поэто­му отож­де­ствле­ние по­ня­тий тут до­пу­сти­мо. Пе­ре­до мной бы­ла по­став­ле­на за­да­ча со­здать при­ло­же­ние, поз­во­ля­ю­щее в ре­аль­ном вре­ме­ни об­ме­ни­вать­ся ин­фор­ма­ци­ей меж­ду сер­ве­ром и кли­ен­том с ми­ни­маль­ны­ми за­держ­ка­ми. Бла­го, при­ло­же­ние это ис­клю­чи­тель­но ин­тра­не­товское и плат­фор­ма ис­поль­зо­ва­ние - Google Chrome, а зна­чит тех­ни­че­ски я не был огра­ни­чен. Ва­ри­ан­тов бы­ло несколь­ко: long-polling HTTP запро­сы, HTTP Streaming или fast-polling, все это на­зы­ва­ет­ся Comet мо­де­лью. И у каж­дой ре­а­ли­за­ции есть свой недо­ста­ток. В спе­ци­фи­ка­ции HTTP/1.1 чет­ко ука­за­но, что брау­зер не дол­жен со­зда­вать од­новре­мен­но бо­лее 2х па­рал­лель­ных со­еди­не­ний к сер­ве­ру. Ко­неч­но, совре­мен­ные брау­зе­ры мо­гут под­дер­жи­вать де­ся­ток connections и подоб­ное на­ру­ше­ние стан­дар­та не яв­ляет­ся фа­таль­ным, но в каж­дом из ва­ри­ан­тов ре­ше­ния есть свои недо­стат­ки. Fast-polling, ко­то­рый пред­став­ляет со­бой много­крат­ное по­вто­ре­ние запро­са (с за­вер­ше­ни­ем сес­сии) со­зда­ет при­лич­ную на­груз­ку на сер­вер, а "ви­ся­щие" запро­сы - ко­сты­ли, ко­то­рые при­во­дят ли­бо к memory-leak (в по­след­нем фа­ер­фок­се сде­лать garbage collector для бу­фе­ра запро­са не удо­су­жи­лись, в хро­ме и са­фа­ри па­мять чи­стит­ся, но толь­ко для запро­сов, воз­вра­ща­ю­щих "Transfer-Encoding: chunked"), к то­му же, контро­ли­ро­вать Comet сес­сию слож­нее, она мо­жет от­ва­ли­вать­ся без ка­кой-ли­бо ви­ди­мой при­чи­ны, да и из­на­чаль­но про­то­кол со­зда­вал­ся не для это­го. Есть еще BOSH, ко­то­рый ис­поль­зу­ет­ся в вы­ше­упо­мя­ну­том XMPP, но он за­то­чен больше под со­зда­ние ча­тов и го­то­вых ре­ше­ний для этой тех­но­ло­гии очень ма­ло. В PaaS Google App Engine, на ко­то­ром рас­по­ло­жен этот блог, есть Channel API, что-то вро­де Comet-а, я да­же пы­тал­ся по­про­бо­вать, но воз­мож­но­сти то­же све­де­ны до ми­ни­му­ма. Ко­неч­но мож­но еще ис­поль­зо­вать оберт­ки во­круг flash socket или java-ап­пле­та, но не это­му учи­ла нас пар­тия.

Вот имен­но та­ким пу­тем, пере­про­бо­вав все вы­ше­на­зван­ные ме­то­ды и оце­нив ре­сур­со­ем­кость, я ре­шил оста­но­вить­ся на server-sent events и web-sockets, ко­то­рые вро­де да­же вы­ве­де­ны из ста­ту­са экс­пере­мен­таль­ных в по­след­них вер­си­ях Webkit-а. На­чнем с SSE, спе­ци­фи­ка­цию мож­но по­чи­тать тут. По су­ти, это тот же HTTP Streaming на сте­рои­дах. Его смысл за­клю­ча­ет­ся в том, что в JavaScript ко­де или DOM со­зда­ет­ся объект EventSource со скрип­том в src атри­бу­те или ме­то­де-конструк­то­ре. Этот скрипт, преж­де всего, дол­жен в контек­сте "ви­ся­ще­го" со­еди­не­ния (хо­тя и не обя­за­тель­но) воз­вра­щать кор­рект­ный Content-Type: text/event-stream и в даль­ней­шем push-ить об­нов­ле­ние кли­ен­ту в ви­де стро­ки data: any_text_info \n\n. То бишь, про­то­кол аб­со­лют­но од­но­сто­ронний, нас это впол­не устра­и­ва­ет, об­щать­ся обрат­но мы мо­жем и че­рез ми­лый серд­цу XHR. Есть в нем и вкус­но­сти, к при­ме­ру, от­вет event: test-remote-event \n data: success \n\n, если ве­рить стан­дар­ту, мож­но об­ра­ба­ты­вать в addEventListener('test-remote-event' ,function(data){}), прав­да, это по­ка толь­ко на бу­ма­ге. В Chrome Canary у ме­ня ра­бо­тал толь­ко event onmessage. Со сто­ро­ны все все­гда ка­жет­ся та­ким кра­си­вым, но тут на­ча­лись пробле­мы, бы­ва­ет, что по­ток про­сто ви­сит. Ни­ка­кой onerror или onclose не вы­зы­вал­ся, сер­вер про­дол­жа­ет ис­прав­но посы­лать дан­ные, что про­ве­ря­ет­ся curl-ом в кон­со­ли, но брау­зе­ру по­фиг, хо­тя ре­старт стра­ни­цы по­мо­га­ет. Про­бо­вал в Chrome Stable - та же  за­мо­роч­ка. ЧЯДНТ? Вто­рая и бо­лее се­рьезная пробле­ма - от­сут­ствие под­держ­ки CORS, Access-Control-Allow-Origin: * от­сы­ла­ет­ся, но брау­зе­рам на не­го с большой ко­ло­коль­ни, они воз­вра­ща­ют DOM Exception 18. Оче­ред­ная брешь в стан­дар­те, как так - XHR ра­бо­та­ет, а EventSource нет. Ко­неч­но, это все при­дир­ки, мож­но бы­ло бы за­вер­нуть тра­фик че­рез proxy_pass в nginx, но в лю­бом слу­чае, оса­док остал­ся. К то­му же, нуж­но уже бы­ло ду­мать о про­верке на­личия \n\n в контен­те, ина­че бы­ла бы ве­ро­ят­ность по­лу­чить по­би­тый от­вет. Та­ким вот пу­тем я и при­шел к WebSockets. Расска­зы­вать что-ли­бо про кли­ент­скую часть не име­ет смыс­ла, есть спе­ци­фи­ка­ция, все это де­ла­ет­ся од­ной стро­кой ко­да, да­вайте луч­ше крат­ко по­го­во­рим, как ре­а­ли­зо­вать подоб­ное на сер­ве­ре. В ка­че­стве server-side для подоб­ных проек­тов я все­гда ре­ко­мен­дую брать NodeJS. Са­ма па­ра­диг­ма event based не­бло­ки­ру­ю­ще­го асин­хронно­го сер­ве­ра иде­аль­но под­хо­дит для вы­со­ко­за­гру­жен­ных API, а этот са­мый ин­тра­нет, он очень большой и запро­сы ре­сур­со­ем­кие. К то­му же, име­ю­ще­го­ся в NPM до­бра с го­ло­вой хва­тит для ре­ше­ния лю­бых за­дач.

Для де­монстра­ции воз­мож­но­стей, я на­пи­сал не­большое при­ло­же­ние. Про­пи­са­но оно по ад­ре­су. Ра­бо­тать долж­но в Chrome, Safari (в том чис­ле и на iPad, iPhone с 4.2) и FF. Но в по­след­нем нуж­но вклю­чить под­держ­ку со­ке­тов. Вот вам ма­ну­ал. Дол­го ду­мал, отку­да бы взять большой по­ток дан­ных для на­гляд­но­го при­ме­ра, как вспо­мнил про Twitter Streaming API. Код го­то­во­го при­ло­же­ния мож­но не­воз­бран­но ска­чать ар­хи­вом. Если де­монстра­ция пере­ста­нет ра­бо­тать - про­шу ме­ня про­стить, мой ста­рень­кий сер­вер очень неж­ный и не­то­ро­пли­вый, и больше вре­ме­ни за­ня­ла компи­ля­ция node, чем про­чте­ние до­ку­мен­та­ции или на­пи­са­ние само­го ко­да. Ра­зу­ме­ет­ся, в server.js нуж­но впи­сать свои ло­гин и па­роль. Во­об­ще, я ис­поль­зую sample по­ток, а вы, про­чи­тав до­ку­мен­та­цию по твит­тер апи и twitter-node, мо­же­те со­здать ка­кой-то по­лезный сер­вис мо­ни­то­рин­га и ана­ли­ти­ки хе­ште­гов, или вы­во­дить в фор­ме гра­ниц Укра­и­ны ава­та­ры поль­зо­ва­телей, на­пи­сав­ших что-то на мо­ве. Фан­та­зии огра­ни­че­ны ис­клю­чи­тель­но воз­мож­но­стя­ми API твит­тера, а их бо­лее чем хва­та­ет.

В за­клю­че­ние, хо­чу ска­зать, что я по­сте­пен­но пере­смат­ри­ваю фор­мат бло­га, если вам по­нра­ви­лась подоб­ная ста­тья - не по­ле­ни­тесь перейти на веб вер­сию, если в РСС чи­та­е­те, и восполь­зо­вать­ся кноп­кой Google +1. Если со­бе­рет­ся немного плю­сов, то в бли­жайшее вре­мя мы по­го­во­рим про WebWorkers, Forms Validation, на­ри­суем муж­ской по­ло­вой хуй в Canvas, напи­шем вра­ща­ю­щий­ся чай­ник на WebGL и по­про­бу­ем на­кла­ды­вать фильтры и по­лу­чать ин­фор­ма­цию из те­га <video>.