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
- Sarlavha
Upgrade. Upgradeulanishini 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
- 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:
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) {
// ...
}
}
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:
@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);
}
}
@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:
@Configuration
class WebConfig {
// ...
@Bean
public WebSocketHandlerAdapter handlerAdapter() {
return new WebSocketHandlerAdapter();
}
}
@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 |
|---|---|
|
|
Kirish xabarlar oqimiga kirishni ta'minlaydi va ulanish yopilgandan so'ng tugashini bildiradi. |
|
|
|
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
WebSocketSessionuchunclosemetodi 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:
class ExampleHandler implements WebSocketHandler {
@Override
public Mono<Void> handle(WebSocketSession session) {
return session.receive()
.doOnNext(message -> {
// ... (2)
})
.concatMap(message -> {
// ... (3)
})
.then();
}
}
- Kirish xabarlar oqimiga kirishni olamiz.
- Har bir xabar ustida qandaydir harakatlarni amalga oshiramiz.
- Xabar mazmunidan foydalanadigan ichki asinxron operatsiyalarni bajaramiz.
- "completes" olganda tugaydigan
Mono<Void>ni qaytaramiz.
class ExampleHandler : WebSocketHandler {
override fun handle(session: WebSocketSession): Mono<Void> {
return session.receive()
.doOnNext {
// ... (2)
}
.concatMap {
// ... (3)
}
.then()
}
}
- Kirish xabarlar oqimiga kirishni olamiz.
- Har bir xabar ustida qandaydir harakatlarni amalga oshiramiz.
- Xabar mazmunidan foydalanadigan ichki asinxron operatsiyalarni bajaramiz.
- "completes" olganda tugaydigan
Mono<Void>ni qaytaramiz.
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:
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);
}
}
- Kirish xabarlar oqimini ishlatamiz.
- Yagona oqim hosil qilib, chiquvchi xabar yaratamiz.
- Ma'lumot olishda davom etar ekanmiz, tugamaydigan
Mono<Void>qaytaramiz.
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)
}
}
- Kirish xabarlar oqimini ishlatamiz.
- Yagona oqim hosil qilib, chiquvchi xabar yaratamiz.
- 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:
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();
}
}
- Kirish xabarlar oqimini ishlatamiz.
- Chiquvchi xabarlarni jo'natamiz.
- Oqimlarni birlashtiramiz va ulardan birortasi tugasa, tugatadigan
Mono<Void>ni qaytaramiz.
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()
}
}
- Kirish xabarlar oqimini ishlatamiz.
- Chiquvchi xabarlarni jo'natamiz.
- 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:
@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);
}
}
@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.
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:
WebSocketClient client = new ReactorNettyWebSocketClient();
URI url = new URI("ws://localhost:8080/path");
client.execute(url, session ->
session.receive()
.doOnNext(System.out::println)
.then());
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.
GO TO FULL VERSION