Internetning umumiy qismi cheklovchi proksi-serverlar orqali sizning nazoratingizdan tashqarida qoldirish mumkin, bu esa Upgrade sarlavhasini uzatishga o'rnatilmaganligi yoki ular uzoq muddatli ulanishlarni yopishi sababli WebSocket bilan o'zaro aloqaga to'sqinlik qilishi mumkin.
Bu muammoni hal qilish uchun WebSocket ni emulyatsiya qilish kerak – ya'ni, avval WebSocket ni ishlatishga urinish, keyin esa HTTP asosidagi texnologiyalarga qaytish kerak, ular WebSocket bilan o'zaro aloqani emulyatsiya qiladi va ilova darajasida bir xil API ni taqdim etadi.
Spring Framework servlet stekida, SockJS protokolining server tomoni (shuningdek mijoz tomoni) qo'llab-quvvatlashini ta'minlaydi.
Qisqacha Tavsif
SockJS ning maqsadi – ilovalarga WebSocket API dan foydalanishga imkon berish, ammo zarurat tug'ilganda yashirin usulda WebSocket bilan bog'liq bo'lmagan alternativlarga qaytish, ilova kodini o'zgartirmasdan.
SockJS quyidagiga ega:
-
SockJS Protokoli, izohlarga ega testlar sifatida belgilangan.
-
JavaScript dan SockJS Mijoz – brauzerlar uchun mijoz kutubxonasi.
-
Shuningdek,
spring-websocketmodulidagi SockJS serverining amalga oshirilishi. -
Java-client SockJS
spring-websocketmodulida (4.1 versiyasidan boshlab).
SockJS brauzerlar uchun mo'ljallangan. U har xil brauzer versiyalarini qo'llab-quvvatlaydigan turli usullarni ishlatadi. SockJS transport mexanizmlari va brauzerlar haqidagi to'liq ro'yxat SockJS mijoz sahifada topiladi. Transport mexanizmlari uchta umumiy toifaga ajratiladi: WebSocket, HTTP protokoli orqali oqim uzatish va uzoq muddatli HTTP-polling. Ushbu toifalar haqidagi sharhni ushbu blog maqolasida ko'ring.
SockJS mijoz GET /infoni yuborib, serverdan asosiy ma'lumotlarni oladi. Shundan so'ng u qaysi transport mexanizmini ishlatishni hal qilishi kerak bo'ladi. Agar iloji bo'lsa, WebSocket protokoli ishlatiladi. Agar buning imkoni yo'q bo'lsa, ko'p brauzerlar kamida bitta HTTP oqim uzatish opsiyasiga ega. Agar bunaqasi ham bo'lmasa, uzoq HTTP-polling ishlatiladi.
Barcha transport so'rovlarida URL manzili quyidagi tuzilishga ega:
https://host:port/myApp/myEndpoint/{server-id}/{session-id}/{transport}
qayerda:
-
{server-id}so'rovlarni klasterda yo'naltirish uchun ishlatiladi, lekin boshqa holatlarda ishlatilmaydi. -
{session-id}SockJS sessiyasiga tegishli HTTP so'rovlarini bog'laydi. -
{transport}transport mexanizmi turini ko'rsatadi (masalan,websocket,xhr-streamingva boshqalar).
WebSocket portokoli orqali transport mexanizmiga faqat bitta HTTP so'rovi kerak bo'ladi, WebSocket aloqa o'rnatilishini tasdiqlash uchun. Barcha keyingi xabarlar bu soket orqali uzatiladi.
HTTP protokoli orqali transport mexanizmlari ko'proq so'rovlarga muhtoj bo'ladi. Masalan, Ajax/XHR oqimi serverdan mijozga xabar uzatish uchun bir uzoq davom etadigan so'rovni ishlatadi va mijozdan serverga xabar uzatish uchun qo'shimcha POST-so'rovlar orqali HTTP. Uzoq polling o'xshash tarzda ishlaydi, faqat har gal serverdan mijozga ma'lumot uzatilgach joriy so'rovni tugatadi.
SockJS oddiy xabar freymlash qo'shadi. Masalan, server dastlab o harfini uzatadi ("open" freymi), xabarlar a["message1", "message2"] formatida uzatiladi (JSON-kodlangan massiv), 25 soniya mobaynida boshqa xabarlar olinmagan bo'lsa h harfini ("heartbeat" freymi) uzatadi va sessiyani yopish uchun c harfini ("close" freymi) uzatadi.
Batafsilroq ma'lumot olish uchun brauzerda misolni ishga tushiring va HTTP so'rovlarini ko'rib chiqing. SockJS mijoz transport mexanizmlarining ro'yxatini log qilish imkonini beradi, shuning uchun har bir mexanizmni ketma-ket ko'rib chiqishingiz mumkin. SockJS mijoz shuningdek brauzer konsoliga foydali xabarlarni chiqarish uchun kerakli bo'lgan bayroqni taqdim etadi. Server tomonda TRACE loglashni org.springframework.web.socket uchun yoqishingiz mumkin. Yanada batafsil ma'lumotni SockJS protokol testida izohli topishingiz mumkin.
SockJS ni yoqish
SockJS ni quyida ko'rsatilganidek Java-konfiguratsiya orqali yoqishingiz mumkin:
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(myHandler(), "/myHandler").withSockJS();
}
@Bean
public WebSocketHandler myHandler() {
return new MyHandler();
}
}
Keyingi misolda avvalgi misolning XML-tengligi ko'rsatilgan:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:websocket="http://www.springframework.org/schema/websocket"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/websocket
https://www.springframework.org/schema/websocket/spring-websocket.xsd">
<websocket:handlers>
<websocket:mapping path="/myHandler" handler="myHandler"/>
<websocket:sockjs/>
</websocket:handlers>
<bean id="myHandler" class="org.springframework.samples.MyHandler"/>
</beans>
Oldingi misol Spring MVC ilovalari uchun mo'ljallangan va DispatcherServlet konfiguratsiyasiga kiritilishi kerak. Ammo Springda WebSocket va SockJS-ni qo'llab-quvvatlash Spring MVC-ga bog'liq emas. SockJsHttpRequestHandler yordamida HTTP protsedurasi bilan ishlaydigan boshqa muhitlarga osonlik bilan integratsiya qilinishi mumkin.
Brauzer tomonda ilovalar sockjs-client (1.0.x versiyasi) dan foydalanishi mumkin. U W3C konsorsiumi tomonidan standarlashtirilgan WebSocket uchun API ni emulyatsiya qiladi va server bilan brauzerda ishga tushganiga qarab eng yaxshi transport variantini tanlashiga imkon beradi. SockJS mijoz sahifasini va brauzer tomonidan qo'llab-quvvatlanadigan transport mexanizmlari ro'yxatini ko'ring. Mijoz shuningdek bir nechta konfiguratsiya optsiyalari bilan ta'minlaydi – masalan, qaysi transport mexanizmlarini qamrab olish kerakligini belgilash uchun.
IE 8 va 9
Internet Explorer 8 va 9 hali ham qo'llaniladi. Ular SockJS ning mavjudligi asosiy sababidir. Ushbu bo'limda ushbu brauzerlarning faoliyati haqida muhim jihatlar ko'rib chiqiladi.
SockJS mijoz XDomainRequest tomonidan qo'llab-quvvatlanadigan IE 8 va 9 da Ajax/XHR oqim uzatishni taqdim etadi. Bu usul turli domaynlarda ishlaydi, ammo cookie uzatishga qo'llab-quvvatlamaydi. Fayllar cookie ko'pincha Java ilovalarining ishlashi uchun zarur. Ammo, SockJS mijoz ko'plab server turlari bilan ishlatilishi mumkin (faqat Java-da emas), unga cookie-lar mavjudligining ahamiyatga egami yoki yo'qligini tushunishi kerak. Agar shunday bo'lsa, SockJS mijoz Ajax/XHR oqimini afzal ko'radi. Aks holda, iframe asosidagi texnikadan foydalaniladi.
SockJS mijozidan birinchi /info so'rovi mijoz tomonidan transport mexanizmini tanlashga ta'sir qilishi mumkin bo'lgan ma'lumotlarni so'raydi. Bunday ma'lumotlarning qismi server ilovasi cookie-lardan foydalanganmi yoki yo'qmi (masalan, autentifikatsiya uchun yoki yopiq sessiyalarda (sticky sessions) kaskadlash uchun). Springda SockJS qo'llab-quvvatlashi sessionCookieNeeded deb nomlangan xususiyatni o'z ichiga oladi. Bu imkoniyat sukut bo'yicha yoqilgan, chunki aksariyat Java ilovalari JSESSIONID cookie dan foydalanadi. Agar sizning ilovangiz bunga muhtoj bo'lmasa, bu opsiyani o'chirib qo'yishingiz va SockJS mijoz IE 8 va 9 da xdr-streaming ni tanlashi kerak.
Agar siz iframe asosidagi transport mexanizmidan foydalansangiz, e'tiborga olish kerakki, ba'zi brauzerlarga HTTP javob sarlavhasida X-Frame-Options ni o'rnatib, ushbu sahifada IFrame dan foydalanishni bloklash buyrug'i berilishi mumkin, bu esa clickjackingdan himoya qilish maqsadida amalga oshiriladi.
Spring Security 3.2+ har javob uchun X-Frame-Options sarlavhasini o'rnatish uchun qo'llab-quvvatlaydi. Sukut bo'yicha Spring Security-ning Java-konfiguratsiyasi DENY qiymatini o'rnatadi. 3.2 versiyasida Spring Security ning XML nomli maydoni bu sarlavhani sukut bo'yicha o'rnatmaydi, ammo uni bunday o'rnatishga moslashtirish mumkin. Kelajakda bu sarlavhani sukut bo'yicha o'rnatishi mumkin.
Spring Security hujjatining "Zaglovka xavfsizligi bo'yicha standartlar" bo'limida X-Frame-Options sarlavhasidagi parametrlarni qanday sozlash bo'yicha batafsil ma'lumot olishingiz mumkin. Yuqoridagi gh-2718 haqida ko'proq bilib oling.
Agar sizning ilovangiz X-Frame-Options javob sarlavhasini qo'shsa (va shunday bo'lishi kerak!) va iframe asosidagi transport mexanizmidan foydalansa, siz bu sarlavhaning qiymatini SAMEORIGIN yoki ALLOW-FROM <origin> qilib o'rnatishingiz kerak bo'ladi. Spring uchun SockJS qo'llab-quvvatlashi shuningdek SockJS mijozining joylashuvi haqida bilishi kerak, chunki u iframe dan yuklanmoqda. Sukut bo'yicha iframe SockJS mijozini CDN dan yuklashga o'rnatilgan. Ilovani joylashgan manzil bilan bir xil URL manzilni ishlatishni sozlash yaxshi fikr.
Keyingi misol Java-konfiguratsiya orqali qanday qilib buni amalga oshirishni ko'rsatadi:
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/portfolio").withSockJS()
.setClientLibraryUrl("http://localhost:8080/myapp/js/sockjs-client.js");
}
// ...
}
XML nom maydoni <websocket:sockjs> elementi orqali shunga o'xshash imkoniyatni taqdim etadi.
devel rejimini yoqing, bu brauzer SockJS so'rovlarini (masalan, iframe) keshlamaslikka imkon beradi. Uni qanday yoqish haqida batafsil ma'lumotni
SockJS mijoz sahifasida ko'ring.
Heartbeat-xabarlari
SockJS protokoli server tomonidan heartbeat-xabarlarini yuborishni talab qiladi, shunda proksi-serverlar ulanishning buzilgan deb hisoblashi mumkin emas. Spring uchun SockJS konfiguratsiyasida siz heartbeat yuborish chastotasini sozlashingiz mumkin bo'lgan heartbeatTime xususiyati mavjud. Sukut bo'yicha heartbeat har 25 sekundda yuboriladi, bu davr mobaynida ushbu ulanish uchun boshqa xabarlar yuborilmagan bo'lsa. Bu 25-sekundlik qiymat IETFning umumiy internet ilovalari uchun tavsiyaga mos keladi.
Springda SockJS qo'llab-quvvatlashi sizga heartbeat-xabarlarini uzatish uchun vazifalarni rejalashtirish imkonini beruvchi TaskSchedulerni sozlash imkonini beradi. Vazifalarni rejalashtiruvchi standart sozlamalari mavjud protsessorlar soniga asoslanadi. Siz ularni o'z ehtiyojlaringizga mos ravishda sozlash imkoniyatlarini hisobga olishingiz kerak.
Mijoz ulanishini yo'qotish
HTTP protokoli orqali oqim uzatish va uzun HTTP-polling transport mexanizmlari SockJS ulanishning uzoqroq vaqt ochiq qolishini talab qiladi. Ushbu usullarning qisqacha tavsiflovchi ushbu blog maqolasida berilgan.
Servlet konteynerlarida bu Servlet 3 ning asinxron qo'llab-quvvatlashi tufayli amalga oshiriladi, bu sizga servlet konteynerlarining ip (thread) dan chiqishga, so'rovni qayta ishlashga va boshqa ipdan javob yozishni davom ettirishga imkon beradi.
Maxsus muammo shundaki, Servlet API mijozni uzilib qolganligini bilish uchun hech qanday xabar bermaydi. eclipse-ee4j/servlet-api#44 ga qarang. Ammo servlet konteynerlari javobga keyingi yozish urinishlarida istisno chiqaradi. Chunki Spring SockJS ning serverdan yuboriladigan heartbeat-xabarlarini qo'llab-quvvatlaydi (sukut bo'yicha har 25 soniyada), bu mijoz bilan ulanishning ko'pincha bu vaqt ichida (yoki agar ko'proq xabarlar yuborilsa, bundan ham tashqari) uzilib qolishini anglatadi.
DISCONNECTED_CLIENT_LOG_CATEGORY dan (bu
AbstractSockJsSessionda aniqlangan) foydalanib siqilgan xabarni jurnalga yozadi. Agar siz stack trace ni ko'rishni istasangiz, jurnalni TRACE qilib o'rnatishingiz mumkin.
SockJS va CORS
Agar turli manbalardan so'rovlarni qabul qilsangiz, SockJS protokoli kross-domenli qo'llab-quvvatlash uchun XHR oqim transporti va so'rov transportida CORS mexanizmini ishlatadi. Shuningdek, agar javobda CORS sarlavhalari mavjud bo'lmasa, ular avtomatik ravishda qo'shiladi. Shu sababli, agar dastur allaqachon CORS ni qo'llab-quvvatlash uchun sozlangan bo'lsa (masalan, servlet filtri orqali), Springdan SockJsService bu qismni o'tkazib yuboradi.
Shuningdek, Springning SockJsServicedagi suppressCors xususiyatini o'rnatish orqali ushbu CORS sarlavhalarini qo'shishni o'chirishingiz mumkin.
SockJS quyidagi sarlavhalarni va qiymatlarini qabul qiladi:
-
Access-Control-Allow-Origin: So'rov sarlavhasidagiOriginqiymatidan boshlanadi. -
Access-Control-Allow-Credentials: Har doimtruega o'rnatiladi. -
Access-Control-Request-Headers: Ekvivalent so'rov sarlavhasidagi qiymatlardan boshlanadi. -
Access-Control-Allow-Methods: Transport mexanizmi tomonidan qo'llab-quvvatlanadigan HTTP metodlari (qarangTransportTypesanasi). -
Access-Control-Max-Age: 31536000 (1 yil) o'rnatadi.
Aniqroq amalga oshirish uchun AbstractSockJsService dagi addCorsHeaders ni va TransportType sanasidagi source code ni ko'ring.
Bundan tashqari, agar CORS konfiguratsiyasi shuni ruxsat etsa, SockJS oxirgi nuqta prefiksi bilan URL manzillarini chiqarishni ko'rib chiqing va Springdan SockJsService ular bilan ishlov berishiga imkon bering.
SockJsClient
Spring SockJS uchun Java mijozini taqdim etadi, bu orqali brauzersiz masofaviy SockJS oxirgi nuqtalariga ulanish mumkin. Bu ayniqsa ikki server o'rtasida umumiy tarmoq orqali ikki tomonli muloqotni ta'minlash uchun foydali bo'lishi mumkin (ya'ni tarmoq proksi-serverlari WebSocket protokolini to'sqinlik qilishi mumkin bo'lganda). Java mijoz SockJS shuningdek test o'tkazish uchun juda foydali (masalan, ko'p sonli bir vaqtning o'zida foydalanuvchilarni simulyatsiya qilish uchun).
Java mijoz SockJS websocket, xhr-streaming va xhr-polling transport mexanizmlarini qo'llab-quvvatlaydi. Boshqalari faqat brauzerda ishlatish uchun mazmunli bo'ladi.
WebSocketTransportni quyidagicha sozlashingiz mumkin:
-
JSR-356 ishlatish muhitida
StandardWebSocketClient. -
Jetty 9+ ga o'rnatilgan WebSocket API dan foydalanib,
JettyWebSocketClient,. -
Springdan har qanday
WebSocketClientamalga oshirilishi.
XhrTransport, ta'rifi bo'yicha, xhr-streaming va xhr-pollingni qo'llab-quvvatlaydi, chunki mijoz tomoni uchun hech qanday farq yo'q, faqat serverga ulanish uchun ishlatiladigan URL manzilidan tashqari. Hozirgi kunda ikkita amalga oshirilishi mavjud:
-
RestTemplateXhrTransportHTTP so'rovlari uchun SpringdanRestTemplateni ishlatadi. -
JettyXhrTransportHTTP so'rovlari uchun JettydanHttpClientni ishlatadi.
Keyingi misolda SockJS mijozini yaratish va SockJS oxirgi nuqtasiga ulanishni qanday amalga oshirish ko'rsatilgan:
List<Transport> transports = new ArrayList<>(2);
transports.add(new WebSocketTransport(new StandardWebSocketClient()));
transports.add(new RestTemplateXhrTransport());
SockJsClient sockJsClient = new SockJsClient(transports);
sockJsClient.doHandshake(new MyWebSocketHandler(), "ws://example.com:8080/sockjs");
SockJsMessageCodec ning o'zingizga mos keladigan amalga oshirilishini sozlashingiz va uni
SockJsClient da sozlashingiz mumkin.
Katta miqdordagi bir vaqtning o'zida foydalanuvchilarni simulyatsiya qilish uchun SockJsClient dan foydalanishda, asosiy HTTP mijozini (XHR transport mexanizmi uchun) yetarli miqdordagi ulanishlar va iplarni ta'minlash uchun sozlash kerak. Keyingi misolda Maven bilan qanday buni amalga oshirish ko'rsatilgan:
HttpClient jettyHttpClient = new HttpClient();
jettyHttpClient.setMaxConnectionsPerDestination(1000);
jettyHttpClient.setExecutor(new QueuedThreadPool(1000));
Keyingi misolda SockJS server tomonida bog'liq bo'lgan xususiyatlar (javadoc'ga qarang), shuningdek sozlanishi kerak bo'lgan ko'rsatilgan:
@Configuration
public class WebSocketConfig extends WebSocketMessageBrokerConfigurationSupport {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/sockjs").withSockJS()
.setStreamBytesLimit(512 * 1024)
.setHttpMessageCacheSize(1000)
.setDisconnectDelay(30 * 1000);
}
// ...
}
streamBytesLimitxususiyatini 512 KB ga (sukut bo'yicha 128 KB -128 * 1024) o'rnatamiz.httpMessageCacheSizexususiyatini 1,000 ga (sukut bo'yicha100) o'rnatamiz.disconnectDelayxususiyatini 30 sekundga (sukut bo'yicha bu besh soniyaga -5 * 1000) o'rnatamiz.
GO TO FULL VERSION