WebSockets

All lectures for UZ purposes
Daraja, Dars
Mavjud

WebSocket protokoli, RFC 6455, mijoz va server o'rtasida to'liq-dupleks ikki tomonlama aloqa kanalini o'rnatish uchun standartlashtirilgan usulni nazarda tutadi. Bu HTTP protokolidan farqli TCP protokoli, lekin u HTTP ustida ishlash uchun mo'ljallangan, 80 va 443 portlarini ishlatadi va mavjud firewall qoidalarini qayta ishlatish imkonini beradi.

WebSocket o'zaro aloqasi HTTP so'rov bilan boshlanadi, u HTTP sarlavhasi Upgrade ni ishlatib, WebSocket protokoliga o'tish uchun yangilanadi. Quyidagi misolda bunday o'zaro aloqa ko'rsatilgan:

GET /spring-websocket-portfolio/portfolio HTTP/1.1
Host: localhost:8080
Upgrade: websocket 
Connection: Upgrade 
Sec-WebSocket-Key: Uc9l9TMkWGbHFD2qnFHltg==
Sec-WebSocket-Protocol: v10.stomp, v11.stomp
Sec-WebSocket-Version: 13
Origin: http://localhost:8080
  1. Sarlavha Upgrade.
  2. Upgrade ulanishini ishlatish.

Oddiy 200 holat kodi o'rniga, WebSocket-ni qo'llab-quvvatlaydigan server quyidagi kabi xabarni chiqaradi:

HTTP/1.1 101 Switching Protocols 
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: 1qVdfYHU9hPOl4JYYNXF623Gzn0=
Sec-WebSocket-Protocol: v10.stomp
  1. Protokolni almashtirish

Ulanishni muvaffaqiyatli tasdiqlagandan so'ng, HTTP yangilanish so'rovi ostida yotgan TCP soketi ochiq qoladi, shunda mijoz va server xabarlarni jo‘natish va qabul qilishni davom ettirishi mumkin.

WebSocket protokolining ishlashiga to'liq kirish ushbu hujjat doirasidan tashqarida. "RFC 6455"ni, HTML5 dagi WebSocket bo'limini yoki internetdagi ko'plab tasvirlarsini va darsliklarni ko'ring.

E'tibor bering, agar WebSocket serveri veb-serveri (masalan, nginx) orqasida ishlayotgan bo'lsa, ehtimol, uni WebSocket serveriga yangilanish so'rovlarini o'tkazish uchun sozlash kerak bo'ladi. Xuddi shunday, agar dastur bulutli muhitda ishlayotgan bo'lsa, bulutli xizmatlar provayderining WebSocket qo'llab-quvvatlashiga oid ko'rsatmalarini tekshiring.

HTTP va WebSocket

WebSocket protokoli HTTP bilan mos ishlash uchun mo'ljallangan va HTTP so'rovlaridan kelib chiqadi, ammo bu ikki protokol butunlay boshqa arxitekturani va dasturni ishlab chiqish modellarini o'z ichiga oladi, deb tushunish muhimdir.

HTTP va REST-da dastur URL-manzillar to'plami sifatida modellashtiriladi. Dastur bilan o'zaro aloqa qilish uchun mijozlar ushbu URL-manzillarga "so'rov-javob" uslubida murojaat qilishadi. Serverlar URL-manzil, metod va HTTP sarlavhalari asosida tegishli ishlov beruvchiga so'rovlarni yo'naltiradi.

Aksincha, WebSocket protokolidagi WebSocketlarda dastlabki ulanish uchun odatda faqat bitta URL-manzildan foydalaniladi. Keyinchalik barcha dastur xabarlari aynan shu TCP ulanishi orqali uzatiladi. Bu shunchaki boshqa bir asinxron, hodisalar bilan boshqariladigan xabarlar almashuvi arxitekturasini bildiradi.

WebSocket shuningdek, past darajadagi transport protokoli bo'lib, u HTTPdan farqli ravishda xabarlar mazmuniga hech qanday semantika belgilamaydi. Bu mijoz va server o'rtasida xabar semantikasi kelishilmaguncha xabarlarni marshrutlash yoki ishlov berishning hech qanday yo'li yo'qligini anglatadi.

WebSocketdagi mijozlar va serverlar xabarlar almashinuvi yuqori darajadagi protokolini (masalan, STOMP) ishlatishni tasdiqlash ulanish so'rovining HTTP tarkibida Sec-WebSocket-Protocol sarlavhasi orqali kelishishi mumkin. Bunday kelishuv bo'lmagan taqdirda, ularga o'z kelishuvlarini ishlab chiqish kerak bo'ladi.

Qachon WebSocket protokoliga ega WebSocketlarni ishlatish kerak?

WebSocket protokolidagi WebSocketlar veb-sahifalarni dinamik va interaktiv qilishlari mumkin. Biroq, ko'p hollarda, Ajax va HTTP oqimi yoki uzun polling (so'rov) kombinatsiyasi oddiy va samarali yechim bo'lishi mumkin.

Masalan, yangiliklar, pochta va ijtimoiy lentalar dinamik ravishda yangilanib turishi kerak, lekin buni har bir necha daqiqada bir marta qilish mumkin. Boshqa tomondan, hamkorlik ilovalari, o'yinlar va moliyaviy ilovalar real vaqtda ko'proq ishlashi kerak.

O'z-o'zidan kechikish hal qiluvchi faktor emas. Agar xabarlar hajmi nisbatan kichik bo'lsa (masalan, tarmoqli uzilishlarni kuzatishda), HTTP protokoli orqali oqim uzatish yoki so'rov osonlikcha samarali yechim bo'lishi mumkin. Past kechikish, yuqori chastota va katta hajm kombinatsiyasi WebSocketdan foydalanish uchun eng yaxshi dalil bo'lishi mumkin.

Shuni ham yodda tutingki, internetda sizning nazoratingizdan tashqarida bo'lgan cheklovchi proxy-serverlar WebSocket muomala qilishlariga to'sqinlik qilishi mumkin, chunki ular Upgrade sarlavhasini uzatishga sozlanmagan, yoki ular uzoq vaqt faoliyat ko'rsatmayotgan ko'rinadigan ulanishlarni yopib qo'yishadi. Bu WebSocketdan himoyalangan tarmoqlar ichida foydalanuvchi ilovalari uchun foydalanishni ommaviy ilovalarga qaraganda osonroq yechim qiladi.

WebSocket API

Spring Framework WebSocket protokoli uchun API-ni taqdim etadi, uni ishlatib WebSocket xabarlarini ishlov beruvchi mijoz va server ilovalarini yozishingiz mumkin.

Server

WebSocket serverini yaratish uchun WebSocketHandler yaratishni boshlashingiz mumkin. Quyidagi misolda buni qanday qilish ko‘rsatilgan:

Java
import org.springframework.web.reactive.socket.WebSocketHandler;
import org.springframework.web.reactive.socket.WebSocketSession;
public class MyWebSocketHandler implements WebSocketHandler {
@Override
public Mono<Void> handle(WebSocketSession session) {
// ...
}
}
Kotlin
import org.springframework.web.reactive.socket.WebSocketHandler
import org.springframework.web.reactive.socket.WebSocketSession
class MyWebSocketHandler : WebSocketHandler {
override fun handle(session: WebSocketSession): Mono<Void> {
// ...
}
}

Keyinchalik uni URL-manzil bilan moslash mumkin:

Java
@Configuration
class WebConfig {
@Bean
public HandlerMapping handlerMapping() {
Map<String, WebSocketHandler> map = new HashMap<>();
map.put("/path", new MyWebSocketHandler());
int order = -1; // annotated controllerlardan oldin
return new SimpleUrlHandlerMapping(map, order);
}
}
Kotlin
@Configuration
class WebConfig {
@Bean
fun handlerMapping(): HandlerMapping {
val map = mapOf("/path" to MyWebSocketHandler())
val order = -1 // annotated controllerlardan oldin
return SimpleUrlHandlerMapping(map, order)
}
}

Agar WebFlux konfiguratsiyasi ishlatilayotgan bo'lsa, boshqa hech narsa qilishning hojati yo'q, aks holda, agar WebFlux konfiguratsiyasi ishlatilmayotgan bo'lsa, unda WebSocketHandlerAdapterni e'lon qilish kerak, bu quyida ko'rsatilgan:

Java
@Configuration
class WebConfig {
// ...
@Bean
public WebSocketHandlerAdapter handlerAdapter() {
return new WebSocketHandlerAdapter();
}
}
Kotlin
@Configuration
class WebConfig {
// ...
@Bean
fun handlerAdapter() =  WebSocketHandlerAdapter()
}

WebSocketHandler

WebSocketHandler dagi handle metodi WebSocketSession ni olib, Mono<Void> ni qaytaradi, bu sessiya bilan ishlov berish tugaganligini bildiradi. Sessiya ikkita oqim orqali ishlanadi, biri kiruvchi va biri chiquvchi xabarlar uchun. Quyidagi jadvalda ikkita metodlar keltirilgan, ular oqimlar bilan ishlaydi:

Metod WebSocketSession Ta'rif

Flux<WebSocketMessage> receive()

Kirish xabarlar oqimiga kirishni ta'minlaydi va ulanish yopilgandan so'ng tugashini bildiradi.

Mono<Void> send(Publisher<WebSocketMessage>)

WebSocketHandler kirish va chiquvchi oqimlarni yagona oqimga birlashtirishi va bu oqimning tugashini ifodalovchi Mono<Void> qaytarishi kerak. Dastur talablari asosida, yagona oqim quyidagi holatlarda tugaydi:

Chiquvchi xabarlar manbasini qabul qiladi, xabarlarni yozadi va yozuv tugagandan so'ng manba tugashi bilan ishini tugatadigan Mono<Void> ni qaytaradi.

  • Qaysi biri birinchi tugasa, kirish yoki chiquvchi xabarlar oqimi tugaydi.

  • Kirish xabarlar oqimi tugaydi (ya'ni, ulanish yopiladi), chiquvchi xabarlar esa cheksiz davom etadi.

  • Tanlangan vaqtda WebSocketSession uchun close metodi orqali.

Agar kirish va chiquvchi xabarlar oqimlari birlashtirilgan bo'lsa, ulanish ochiq ekanligini tekshirishga hojat yo'q, chunki Reactive Streams faoliyat tugaganligi signalini beradi. Kirish oqimi tugash yoki xatolik signali oladi, chiquvchi oqim esa bekor qilish signali oladi.

Eng oddiy ishlov beruvchi realizatsiya – kirish oqimini ishlov beradigan realizatsiya. Quyidagi misolda bunday realizatsiya ko'rsatilgan:

Java
class ExampleHandler implements WebSocketHandler {
@Override
public Mono<Void> handle(WebSocketSession session) {
return session.receive()            
        .doOnNext(message -> {
            // ... (2)
        })
        .concatMap(message -> {
            // ... (3)
        })
        .then();                    
}
}
  1. Kirish xabarlar oqimiga kirishni olamiz.
  2. Har bir xabar ustida qandaydir harakatlarni amalga oshiramiz.
  3. Xabar mazmunidan foydalanadigan ichki asinxron operatsiyalarni bajaramiz.
  4. "completes" olganda tugaydigan Mono<Void>ni qaytaramiz.
Kotlin
class ExampleHandler : WebSocketHandler {
override fun handle(session: WebSocketSession): Mono<Void> {
return session.receive()            
        .doOnNext {
            // ... (2)
        }
        .concatMap {
            // ... (3)
        }
        .then()                     
}
}
  1. Kirish xabarlar oqimiga kirishni olamiz.
  2. Har bir xabar ustida qandaydir harakatlarni amalga oshiramiz.
  3. Xabar mazmunidan foydalanadigan ichki asinxron operatsiyalarni bajaramiz.
  4. "completes" olganda tugaydigan Mono<Void>ni qaytaramiz.
Ichki asinxron operatsiyalar uchun ba'zida message.retain() chaqirish kerak bo'lishi mumkin, bu muhim serverlarda foydalaniluvchi biriktirilgan ma'lumotlar buferlari (masalan, Nettyda) ni ishlatishda. Aks holda, ma'lumotlar o'qilmaganigacha bufer ma'lumotlari ozod qilinishi mumkin.

Quyidagi realizatsiya kirish va chiquvchi oqimlarni birlashtiradi:

Java
class ExampleHandler implements WebSocketHandler {
@Override
public Mono<Void> handle(WebSocketSession session) {
Flux<WebSocketMessage> output = session.receive()               
        .doOnNext(message -> {
            // ...
        })
        .concatMap(message -> {
            // ...
        })
        .map(value -> session.textMessage("Echo " + value));    
return session.send(output);                                    
}
}
  1. Kirish xabarlar oqimini ishlatamiz.
  2. Yagona oqim hosil qilib, chiquvchi xabar yaratamiz.
  3. Ma'lumot olishda davom etar ekanmiz, tugamaydigan Mono<Void> qaytaramiz.
Kotlin
class ExampleHandler : WebSocketHandler {
override fun handle(session: WebSocketSession): Mono<Void> {
val output = session.receive()                      
        .doOnNext {
            // ...
        }
        .concatMap {
            // ...
        }
        .map { session.textMessage("Echo $it") }    
return session.send(output)                         
}
}
  1. Kirish xabarlar oqimini ishlatamiz.
  2. Yagona oqim hosil qilib, chiquvchi xabar yaratamiz.
  3. Ma'lumot olishda davom etar ekanmiz, tugamaydigan Mono<Void> qaytaramiz.

Kirish va chiquvchi oqimlar mustaqil bo'lishi va faqat tugatish uchun birlashtirilishi mumkin, bu quyidagi misolda ko'rsatilgan:

Java
class ExampleHandler implements WebSocketHandler {
@Override
public Mono<Void> handle(WebSocketSession session) {
Mono<Void> input = session.receive()                                
        .doOnNext(message -> {
            // ...
        })
        .concatMap(message -> {
            // ...
        })
        .then();
Flux<String> source = ... ;
Mono<Void> output = session.send(source.map(session::textMessage)); 
return Mono.zip(input, output).then();                              
}
}
  1. Kirish xabarlar oqimini ishlatamiz.
  2. Chiquvchi xabarlarni jo'natamiz.
  3. Oqimlarni birlashtiramiz va ulardan birortasi tugasa, tugatadigan Mono<Void> ni qaytaramiz.
Kotlin
class ExampleHandler : WebSocketHandler {
override fun handle(session: WebSocketSession): Mono<Void> {
val input = session.receive()                                   
        .doOnNext {
            // ...
        }
        .concatMap {
            // ...
        }
        .then()
val source: Flux<String> = ...
val output = session.send(source.map(session::textMessage))     
return Mono.zip(input, output).then()                           
}
}
  1. Kirish xabarlar oqimini ishlatamiz.
  2. Chiquvchi xabarlarni jo'natamiz.
  3. Oqimlarni birlashtiramiz va ulardan birortasi tugasa, tugatadigan Mono<Void> ni qaytaramiz.

DataBuffer

DataBuffer WebFluxdagi baytli buferni ifodalash. Ba'zi serverlarda, masalan, Nettyda, ma'lumotlar buferlari birlashtirilgan va havola hisoblanadi va ular iste'mol qilingach ozod qilinishi kerakligi muhimdir, bu xotira sizmasligi oldini olish uchun.

Nettyda ishlaganda, agar kirish ma'lumotlar buferlarini ularni ozod qilmasdan qayta ishlatish kerak bo'lsa, dasturlardan foydalanish kerak DataBufferUtils.retain(dataBuffer), keyin buferlardan ma'lumotlar iste'mol qilinganda DataBufferUtils.release(dataBuffer)ni ishlatish kerak.

Aloqani tasdiqlash

WebSocketHandlerAdapter WebSocket xizmatiga delegatsiya qiladi. Odatiy holatda bu HandshakeWebSocketServicening bir nusxasi bo'lib, bu WebSocket so'rovi asosiy tekshiruvlarini amalga oshiradi, keyin foydalanilayotgan server uchun RequestUpgradeStrategydan foydalanadi. Hozirgi kunda Reactor Netty, Tomcat, Jetty va Undertow uchun ichki qo'llab-quvvatlash mavjud.

HandshakeWebSocketService sessionAttributePredicate xususiyatini ochadi, bu WebSession dan atributlarni chiqarib olib, ularni WebSocketSession atributlariga qo‘shish uchun imkon beradigan Predicate<String> ni o'rnatish imkonini beradi.

Server konfiguratsiyasi

Har bir server uchun RequestUpgradeStrategy asosiy WebSocket server mexanizmi uchun o'ziga xos konfiguratsiyani ochadi. Java WebFlux konfiguratsiyasidan foydalanilganda, bunday xususiyatlarni sozlash mumkin, yoki WebFlux konfiguratsiyasidan foydalanilmasa, quyida keltirilgan xususiyatlardan foydalanish mumkin:

Java
@Configuration
class WebConfig {
@Bean
public WebSocketHandlerAdapter handlerAdapter() {
return new WebSocketHandlerAdapter(webSocketService());
}
@Bean
public WebSocketService webSocketService() {
TomcatRequestUpgradeStrategy strategy = new TomcatRequestUpgradeStrategy();
strategy.setMaxSessionIdleTimeout(0L);
return new HandshakeWebSocketService(strategy);
}
}
Kotlin
@Configuration
class WebConfig {
@Bean
fun handlerAdapter() =
    WebSocketHandlerAdapter(webSocketService())
@Bean
fun webSocketService(): WebSocketService {
val strategy = TomcatRequestUpgradeStrategy().apply {
    setMaxSessionIdleTimeout(0L)
}
return HandshakeWebSocketService(strategy)
}
}

Serveringiz uchun yangilanish strategiyasini ko'rib chiqing, qaysi variantlar mavjudligini bilib oling. Hozirgi kunda faqat Tomcat va Jetty bunday imkoniyatlarni taqdim etadi.

CORS

CORSni sozlash va WebSocket nuqtasiga kirishni cheklashning eng oson yo'li WebSocketHandlerni CorsConfigurationSourceni amalga oshiruvchi qilib qo‘yish va ruxsat etilgan manbalar, sarlavhalar va boshqa ma'lumotlardan foydalanib, CorsConfigurationni qaytarishdir. Agar bu mumkin bo'lmasa, shuningdek, SimpleUrlHandler uchun corsConfigurations xususiyatini URL-shablon orqali CORS sozlamalarini o'rnatishga ham ruxsat beriladi. Agar ikkalasi ham o'rnatilgan bo'lsa, ular CorsConfiguration uchun combine metod orqali birlashtiriladi.

Mijoz

Spring WebFlux WebSocketClient abstraksiyasini taqdim etadi, Reactor Netty, Tomcat, Jetty, Undertow va standart Java (ya'ni JSR-356) uchun realizatsiyalari bilan.

Tomcat mijozi aslida standart Java mijozini kengaytmasi bo'lib, WebSocketSessionni qayta ishlashda Tomcatga xos bo'lgan API-dan foydalanishga imkon beruvchi qo‘shimcha funksiyani taqdim etadi, bu xabarlarni olishni to‘xtatishga imkon beradi.

WebSocket sessiyasini boshlash uchun mijoz nusxasini yaratib, uning execute metodini ishlatish mumkin:

Java
WebSocketClient client = new ReactorNettyWebSocketClient();
URI url = new URI("ws://localhost:8080/path");
client.execute(url, session ->
session.receive()
        .doOnNext(System.out::println)
        .then());
Kotlin
val client = ReactorNettyWebSocketClient()
val url = URI("ws://localhost:8080/path")
client.execute(url) { session ->
    session.receive()
            .doOnNext(::println)
    .then()
}

Ba'zi mijozlar, masalan, Jetty, Lifecycleni amalga oshirishadi va foydalanishdan oldin ularni to'xtatish va ishga tushirish kerak bo'ladi. Barcha mijozlar asosiy WebSocket mijoz konfiguratsiyasiga tegishli konstruktor parametrlarga ega.

Izohlar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION