STOMP ile WebSocket Uygulaması için Spring Boot'u Kullanma

Yayınlanan: 2022-03-11

WebSocket protokolü, uygulamanızın gerçek zamanlı mesajları işlemesini sağlamanın yollarından biridir. En yaygın alternatifler, uzun yoklama ve sunucu tarafından gönderilen olaylardır. Bu çözümlerin her birinin avantajları ve dezavantajları vardır. Bu yazımda sizlere Spring Boot Framework ile WebSockets nasıl uygulanacağını göstereceğim. Hem sunucu tarafı hem de istemci tarafı kurulumunu ele alacağım ve birbirimizle iletişim kurmak için WebSocket üzerinden STOMP protokolünü kullanacağız.

Sunucu tarafı tamamen Java ile kodlanacaktır. Ancak, istemci söz konusu olduğunda, WebSockets istemcileri genellikle ön uç uygulamalara gömülü olduğundan, hem Java'da hem de JavaScript'te (SockJS) yazılmış parçacıkları göstereceğim. Kod örnekleri, mesajların pub-sub modelini kullanarak birden çok kullanıcıya nasıl yayınlanacağını ve yalnızca tek bir kullanıcıya nasıl mesaj gönderileceğini gösterecektir. Makalenin bir sonraki bölümünde, WebSockets'in güvenliğini ve WebSocket tabanlı çözümümüzün, ortam WebSocket protokolünü desteklemediğinde bile çalışır durumda kalmasını nasıl sağlayabileceğimizi kısaca tartışacağım.

Ayrı bir makale için yeterince karmaşık bir konu olduğundan, WebSockets'in güvenliği konusuna burada yalnızca kısaca değinileceğini lütfen unutmayın. Bu ve Üretimde WebSocket'te değindiğim diğer birkaç faktörden dolayı mı? bölüm sonunda, bu kurulumu üretimde kullanmadan önce değişiklik yapmanızı tavsiye ederim , güvenlik önlemleri alınmış üretime hazır bir kurulum için sonuna kadar okuyun.

WebSocket ve STOMP Protokolleri

WebSocket protokolü, uygulamalar arasında çift yönlü iletişim uygulamanıza olanak tanır. HTTP'nin yalnızca ilk el sıkışma için kullanıldığını bilmek önemlidir. Bu gerçekleştikten sonra, HTTP bağlantısı bir WebSocket tarafından kullanılan yeni açılan bir TCP/IP bağlantısına yükseltilir.

WebSocket protokolü oldukça düşük seviyeli bir protokoldür. Bir bayt akışının çerçevelere nasıl dönüştürüleceğini tanımlar. Bir çerçeve bir metin veya ikili bir mesaj içerebilir. İletinin kendisi, nasıl yönlendirileceği veya işleneceği hakkında herhangi bir ek bilgi sağlamadığından, ek kod yazmadan daha karmaşık uygulamaları uygulamak zordur. Neyse ki WebSocket özelliği, daha yüksek bir uygulama düzeyinde çalışan alt protokollerin kullanılmasına izin verir. Spring Framework tarafından desteklenen bunlardan biri STOMP.

STOMP, başlangıçta Ruby, Python ve Perl gibi komut dosyası dilleri için kurumsal mesaj aracılarına bağlanmak için oluşturulmuş basit bir metin tabanlı mesajlaşma protokolüdür. STOMP sayesinde, farklı dillerde geliştirilen müşteriler ve brokerler birbirlerine mesaj gönderip alabilirler. WebSocket protokolüne bazen Web için TCP denir. Benzer şekilde, STOMP, Web için HTTP olarak adlandırılır. WebSockets çerçevelerine eşlenen bir avuç çerçeve tipini tanımlar, örneğin, CONNECT , SUBSCRIBE , UNSUBSCRIBE , ACK veya SEND . Bu komutlar bir yandan iletişimi yönetmek için çok kullanışlıdır, diğer yandan mesaj onayı gibi daha karmaşık özelliklere sahip çözümler uygulamamıza izin verir.

Sunucu tarafı: Spring Boot ve WebSockets

WebSocket sunucu tarafını oluşturmak için Java'da bağımsız ve web uygulamalarının geliştirilmesini önemli ölçüde hızlandıran Spring Boot çerçevesini kullanacağız. Spring Boot, Java WebSocket API standardı (JSR-356) ile uyumlu olan spring-WebSocket modülünü içerir.

WebSocket sunucu tarafını Spring Boot ile uygulamak çok karmaşık bir iş değildir ve tek tek inceleyeceğimiz sadece birkaç adım içerir.

Adım 1. İlk olarak, WebSocket kitaplığı bağımlılığını eklememiz gerekiyor.

 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency>

İletilen mesajlar için JSON formatını kullanmayı planlıyorsanız, GSON veya Jackson bağımlılığını da dahil etmek isteyebilirsiniz. Büyük olasılıkla, örneğin Spring Security gibi bir güvenlik çerçevesine de ihtiyacınız olabilir.

Adım 2. Ardından Spring'i WebSocket ve STOMP mesajlaşmasını etkinleştirecek şekilde yapılandırabiliriz.

 Configuration @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/mywebsockets") .setAllowedOrigins("mydomain.com").withSockJS(); } @Override public void configureMessageBroker(MessageBrokerRegistry config){ config.enableSimpleBroker("/topic/", "/queue/"); config.setApplicationDestinationPrefixes("/app"); } }

configureMessageBroker yöntemi iki şey yapar:

  1. İleti göndermek ve almak için bir veya daha fazla hedef içeren bellek içi ileti aracısını oluşturur. Yukarıdaki örnekte, tanımlanan iki hedef öneki vardır: topic ve queue . Pub-sub modeli aracılığıyla tüm abone istemcilere taşınacak mesajların hedeflerine topic başlığı eklenmesi gerektiği kuralına uyarlar. Öte yandan, özel mesajların hedeflerinin önüne genellikle queue eklenir.
  2. Bir denetleyicide uygulayacağınız @MessageMapping ile açıklamalı yöntemlerle işlenen hedefleri filtrelemek için kullanılan önek app tanımlar. Kontrolör, mesajı işledikten sonra aracıya gönderir.

Spring Boot WebSocket: Mesajlar sunucu tarafında nasıl işlenir?

İletiler sunucu tarafında nasıl işlenir (kaynak: Spring belgeleri)


Yukarıdaki parçaya geri dönersek—muhtemelen withSockJS() yöntemine yapılan bir çağrıyı fark etmişsinizdir—bu, SockJS yedek seçeneklerini etkinleştirir. İşleri kısa tutmak için, WebSocket protokolü bir internet tarayıcısı tarafından desteklenmese bile WebSocket'lerimizin çalışmasına izin verecektir. Bu konuyu biraz daha ayrıntılı olarak tartışacağım.

Açıklığa kavuşturulması gereken bir şey daha var - neden uç setAllowedOrigins() yöntemini çağırıyoruz. WebSocket ve SockJS'nin varsayılan davranışı yalnızca aynı kaynaktan gelen istekleri kabul etmek olduğundan genellikle gereklidir. Dolayısıyla, istemciniz ve sunucu tarafı farklı etki alanları kullanıyorsa, aralarındaki iletişime izin vermek için bu yöntemin çağrılması gerekir.

Adım 3 . Kullanıcı isteklerini işleyecek bir denetleyici uygulayın. Alınan mesajı belirli bir konuya abone olan tüm kullanıcılara yayınlayacaktır.

İşte /topic/news hedefine mesaj gönderen örnek bir yöntem.

 @MessageMapping("/news") @SendTo("/topic/news") public void broadcastNews(@Payload String message) { return message; }

@SendTo ek açıklaması yerine, denetleyicinizin içinde otomatik olarak SimpMessagingTemplate de kullanabilirsiniz.

 @MessageMapping("/news") public void broadcastNews(@Payload String message) { this.simpMessagingTemplate.convertAndSend("/topic/news", message) }

Sonraki adımlarda, Spring Security çerçevesinden ResourceServerConfigurerAdapter veya WebSecurityConfigurerAdapter gibi uç noktalarınızın güvenliğini sağlamak için bazı ek sınıflar eklemek isteyebilirsiniz. Ayrıca, iletilen JSON'un nesnelerle eşlenebilmesi için mesaj modelini uygulamak genellikle faydalıdır.

WebSocket İstemcisini Oluşturma

Bir istemciyi uygulamak daha da basit bir iştir.

Adım 1. Spring STOMP istemcisini otomatik olarak bağlayın.

 @Autowired private WebSocketStompClient stompClient;

Adım 2. Bir bağlantı açın.

 StompSessionHandler sessionHandler = new CustmStompSessionHandler(); StompSession stompSession = stompClient.connect(loggerServerQueueUrl, sessionHandler).get();

Bu yapıldıktan sonra, bir hedefe mesaj göndermek mümkündür. Mesaj, bir konuya abone olan tüm kullanıcılara gönderilecektir.

 stompSession.send("topic/greetings", "Hello new user");

Mesajlara abone olmak da mümkündür.

 session.subscribe("topic/greetings", this); @Override public void handleFrame(StompHeaders headers, Object payload) { Message msg = (Message) payload; logger.info("Received : " + msg.getText()+ " from : " + msg.getFrom()); }

Bazen yalnızca özel bir kullanıcıya mesaj göndermek gerekir (örneğin bir sohbeti uygularken). Ardından, istemci ve sunucu tarafı, bu özel görüşmeye ayrılmış ayrı bir hedef kullanmalıdır. Hedefin adı, örneğin, /queue/chat-user123 gibi genel bir hedef adına benzersiz bir tanımlayıcı eklenerek oluşturulabilir. Bu amaçla HTTP Oturumu veya STOMP oturum tanımlayıcıları kullanılabilir.

Bahar, özel mesaj göndermeyi çok daha kolay hale getiriyor. Yalnızca @SendToUser ile bir Controller'ın yöntemine açıklama eklememiz gerekiyor. Ardından, bu hedef, bir oturum tanımlayıcısına dayanan UserDestinationMessageHandler tarafından işlenecektir. İstemci tarafında, bir istemci /user ön ekine sahip bir hedefe abone olduğunda, bu hedef bu kullanıcı için benzersiz bir hedefe dönüştürülür. Sunucu tarafında, bir kullanıcı hedefi, bir kullanıcının Principal göre çözümlenir.

@SendToUser açıklamalı örnek sunucu tarafı kodu:

 @MessageMapping("/greetings") @SendToUser("/queue/greetings") public String reply(@Payload String message, Principal user) { return "Hello " + message; }

Veya SimpMessagingTemplate kullanabilirsiniz :

 String username = ... this.simpMessagingTemplate.convertAndSendToUser(); username, "/queue/greetings", "Hello " + username);

Şimdi yukarıdaki örnekte Java koduyla gönderilebilecek özel mesajları alabilen bir JavaScript (SockJS) istemcisinin nasıl uygulanacağına bakalım. WebSockets'in HTML5 belirtiminin bir parçası olduğunu ve çoğu modern tarayıcı tarafından desteklendiğini bilmek önemlidir (Internet Explorer, sürüm 10'dan beri bunları desteklemektedir).

 function connect() { var socket = new SockJS('/greetings'); stompClient = Stomp.over(socket); stompClient.connect({}, function (frame) { stompClient.subscribe('/user/queue/greetings', function (greeting) { showGreeting(JSON.parse(greeting.body).name); }); }); } function sendName() { stompClient.send("/app/greetings", {}, $("#name").val()); }

Muhtemelen belirttiğiniz gibi, özel mesajları almak için, istemcinin /user ön ekine sahip /queue/greetings genel hedefine abone olması gerekir. Benzersiz tanımlayıcılarla uğraşması gerekmez. Ancak, istemcinin daha önce uygulamada oturum açması gerekir, bu nedenle sunucu tarafındaki Principal nesne başlatılır.

WebSockets'in Güvenliğini Sağlama

Birçok web uygulaması, tanımlama bilgisi tabanlı kimlik doğrulaması kullanır. Örneğin, belirli sayfalara veya Denetleyicilere erişimi yalnızca oturum açmış kullanıcılarla kısıtlamak için Spring Security kullanabiliriz. Kullanıcı güvenlik bağlamı, daha sonra o kullanıcı için oluşturulan WebSocket veya SockJS oturumlarıyla ilişkilendirilen tanımlama bilgisi tabanlı HTTP oturumu aracılığıyla korunur. WebSockets uç noktaları, örneğin WebSecurityConfigurerAdapter diğer istekler gibi güvence altına alınabilir.

Günümüzde, web uygulamaları genellikle arka uç olarak REST API'lerini ve kullanıcı kimlik doğrulaması ve yetkilendirme için OAuth/JWT belirteçlerini kullanır. WebSocket protokolü, HTTP anlaşması sırasında sunucuların istemcilerin kimliğini nasıl doğrulaması gerektiğini açıklamaz. Pratikte, bu amaç için standart HTTP başlıkları (ör. Yetkilendirme) kullanılır. Ne yazık ki, tüm STOMP istemcileri tarafından desteklenmemektedir. Spring Java'nın STOMP istemcisi, el sıkışma için başlıkların ayarlanmasına izin verir:

 WebSocketHttpHeaders handshakeHeaders = new WebSocketHttpHeaders(); handshakeHeaders.add(principalRequestHeader, principalRequestValue);

Ancak SockJS JavaScript istemcisi, bir SockJS isteğiyle yetkilendirme başlığının gönderilmesini desteklemez. Ancak, bir belirteç iletmek için kullanılabilecek sorgu parametrelerinin gönderilmesine izin verir. Bu yaklaşım, sunucu tarafında belirteci sorgu parametrelerinden okuyacak ve doğrulayacak özel kod yazmayı gerektirir. Ciddi bir güvenlik ihlaline yol açabileceğinden, belirteçlerin isteklerle birlikte günlüğe kaydedilmediğinden (veya günlüklerin iyi korunduğundan) emin olmak da önemlidir.

SockJS Geri Dönüş Seçenekleri

WebSocket ile entegrasyon her zaman sorunsuz gitmeyebilir. Bazı tarayıcılar (örneğin, IE 9) WebSockets'i desteklemez. Ayrıca, kısıtlayıcı proxy'ler HTTP yükseltmesini gerçekleştirmeyi imkansız hale getirebilir veya çok uzun süre açık olan bağlantıları kesebilir. Bu gibi durumlarda, SockJS kurtarmaya gelir.

SockJS aktarımları üç genel kategoriye ayrılır: WebSockets, HTTP Akışı ve HTTP Uzun Yoklama. İletişim, SockJS'nin sunucudan temel bilgileri almak için GET /info göndermesiyle başlar. SockJS, cevaba göre kullanılacak aktarıma karar verir. İlk seçenek WebSockets. Desteklenmiyorlarsa, mümkünse Akış kullanılır. Bu seçenek de mümkün değilse, taşıma yöntemi olarak Yoklama seçilir.

WebSocket Üretimde mi?

Bu kurulum işe yarasa da "en iyisi" değildir. Spring Boot, STOMP protokolüyle (örneğin, ActiveMQ, RabbitMQ) herhangi bir tam teşekküllü mesajlaşma sistemini kullanmanıza izin verir ve harici bir aracı, kullandığımız basit aracıdan daha fazla STOMP işlemini (örneğin, onaylar, makbuzlar) destekleyebilir. WebSocket Üzerinden STOMP , WebSockets ve STOMP protokolü hakkında ilginç bilgiler sağlar. STOMP protokolünü işleyen ve üretimde kullanmak için daha iyi bir çözüm olabilecek mesajlaşma sistemlerini listeler. Özellikle çok sayıda istek nedeniyle ileti aracısının kümelenmesi gerekiyorsa. (Spring'in basit mesaj komisyoncusu kümeleme için uygun değildir.) Ardından, WebSocketConfig içindeki basit aracıyı etkinleştirmek yerine, mesajları harici bir mesaj aracısına ve bu aracıdan ileten Stomp aracısı geçişini etkinleştirmek gerekir. Özetlemek gerekirse, harici bir mesaj aracısı daha ölçeklenebilir ve sağlam bir çözüm oluşturmanıza yardımcı olabilir.

Java Geliştirici yolculuğunuza Spring Boot'u keşfetmeye devam etmeye hazırsanız, daha sonra Spring Boot for OAuth2 ve JWT REST Protection'ı Kullanmayı okumayı deneyin.